Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/piotrjustyna/mercury-pure-functional
Functional type of code with explicitly separated pure and effectful responsibilities.
https://github.com/piotrjustyna/mercury-pure-functional
Last synced: 2 days ago
JSON representation
Functional type of code with explicitly separated pure and effectful responsibilities.
- Host: GitHub
- URL: https://github.com/piotrjustyna/mercury-pure-functional
- Owner: PiotrJustyna
- License: mit
- Created: 2021-09-17T12:18:32.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2021-10-08T11:18:28.000Z (about 3 years ago)
- Last Synced: 2023-08-02T13:15:58.146Z (over 1 year ago)
- Language: F#
- Size: 36.1 KB
- Stars: 1
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# mercury-pure-functional
Functional type of code with explicitly separated pure and effectful responsibilities.
## output
```
12:01:33.288: function is starting...
12:01:34.507: "google.com": 8768 days since domain creation, 738 days since domain last updated, 2552 until domain expires
12:01:34.507: function execution finished
```## null checking
Here is the order in which the information flows and the types are constructed:
`xml -> WhoisRecord -> WhoisResponse`
Unlike C#, F# does not treat `null` as a first-class citizen. Presence or absence of values is usually preferred to be indicated using the `Option` type. However, when interfacing C# code, one should remember that values arriving from C# could be `null`. To handle such scenarios, I initially proposed `null` checking on the C# side, but there are better ways to `null` check and in the current version of the code, I propose to `null` check on the F# side.
To achieve this, our models are now divided into two categories:
* [records](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/records) - simple, ideally immutable F# models. In our case, records are used internally, in F#-F# interactions. Example:
```f#
type WhoisResponse =
{ Domain: string
DomainAgeInDays: Option
DomainLastUpdatedInDays: Option
DomainExpirationInDays: Option
AuditCreated: Option
AuditUpdated: Option }
```
What is worth noting is that most fields in this record are of type `Option`, which indicates that they could be missing (as they are mapped from a type which models our xml, and the xml is not guaranteed to be always correct).* [classes](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/classes) - more complex, C#-like models, ideal for interfacing C# code. In our case, classes are used in C#-F# interactions, specifically to deserialize xml. Example:
```f#
[]
type Audit() =
member val createdDate = String.Empty with get, set
member val updatedDate = String.Empty with get, set
```
What becomes immediately obvious is:
* parameterless constructor exposed
* type is decorated with the `AllowNullLiteral` attribute indicating that its values can be `null` (problems with parsing, incorrectly defined xml, etc.)
* properties (`createdDate`, `updatedDate`) defined explicitly and made mutable (`with get, set`) to be automatically set during deserialization.All unit tests validating null checking are also done on the F# side.
## unit testing
This repository uses [xunit](https://github.com/xunit/xunit) + [unquote](https://github.com/SwensenSoftware/unquote).
The repository used to use [fsunit](https://fsprojects.github.io/FsUnit/) but unquote provides better:
* type checking strictness
example (`differenceInDays` returns `float`):
* fsunit:
```f#
MercuryLibrary.BusinessLogic.differenceInDays start finish
|> should equal -1
```
The test passes even though `-1` and the value returned by `differenceInDays` are of different types.* unquote:
```f#
test <@ MercuryLibrary.BusinessLogic.differenceInDays start finish = -1.0 @>
```The test passes provided that the `-1` is strictly represented as a `float` (`-1.0`, not `-1`), so that both compared values are of exactly the same type.
* error messages for failing tests
sample failing test```f#
test <@ whoisResponse.DomainAgeInDays < (now - DateTime.Parse(oneDayAgo)).TotalDays @>
```
output:
```
MercuryLibraryTests.MappersTests.toWhoisResponse all properties correct
Xunit.Sdk.TrueException
whoisResponse.DomainAgeInDays < (let mutable copyOfStruct = now - DateTime.Parse(oneDayAgo) in copyOfStruct.TotalDays)
{ Domain = "google.com"
DomainAgeInDays = 1.000006465
DomainLastUpdatedInDays = 2.000006465
DomainExpirationInDays = 2.999993535
AuditCreated = 09/24/2021 10:12:57
AuditUpdated = 09/23/2021 10:12:57 }.DomainAgeInDays < (let mutable copyOfStruct = 2021-09-28T10:12:57.5585520Z (Utc) - DateTime.Parse("09/27/2021 10:12:57") in copyOfStruct.TotalDays)
1.000006465 < (let mutable copyOfStruct = 2021-09-28T10:12:57.5585520Z (Utc) - 2021-09-27T10:12:57.0000000 (Unspecified) in copyOfStruct.TotalDays)
1.000006465 < (let mutable copyOfStruct = 1.00:00:00.5585520 in copyOfStruct.TotalDays)
1.000006465 < 1.000006465
false
Expected: True
Actual: False
at MercuryLibraryTests.MappersTests.toWhoisResponse all properties correct() in /Users/justpi01/Documents/code/mercury-pure-functional/MercuryLibraryTests/MappersTests.fs:line 41
```