Reputation: 13089
I have a union type like this
type AccountCreated =
{ Owner: string
AccountId: Guid
CreatedAt: DateTimeOffset
StartingBalance: decimal }
type AccountDebited =
{ To: Guid
From: Guid
Description: string
Time: DateTimeOffset
Amount: decimal }
type AccountCredited =
{ To: Guid
From: Guid
Description: string
Time: DateTimeOffset
Amount: decimal }
type AccountEvent =
| Created of AccountCreated
| AccountCredited of AccountCredited
| AccountDebited of AccountDebited
And another Union type like this:
type RegisteredAccount = {
Owner: string
Balance: decimal
AccountId: Guid }
type Account =
| Unregistered
| Registered of RegisteredAccount
There's a function evolve
:
let evolve state event: Account =
match event with
| Created accountCreated ->
{ AccountId = accountCreated.AccountId
Owner = accountCreated.Owner
Balance = accountCreated.StartingBalance }
| AccountDebited accountDebited ->
match state with
| Registered s ->
{ s with
Balance = s.Balance - accountDebited.Amount }
| _ -> failwith "unregistered account can't be debited"
| _ -> failwith "todo: other cases"
evolve
should be used using List.fold
:
let build = List.fold evolve
let rebuild = build Unregistered
If I don't explicitely specify the return type of evolve
as Account
, I get this error for the line let build = List.fold evolve
:
Type mismatch. Expecting a 'Account -> AccountEvent -> Account' but given a 'Account -> AccountEvent -> RegisteredAccount' The type 'Account' does not match the type 'RegisteredAccount'
If set the return type of evolve
, I get the compiler error in the pattern match for Created
and one similar for Registered
inside the AccountDebited
pattern match.
This expression was expected to have type 'Account' but here has type 'RegisteredAccount'
How can I solve this?
Upvotes: 0
Views: 75
Reputation: 243051
I think the issue in your code is that the evolve
function tries to return a value of the RegisteredAccount
record type rather than a value of the Account
union type.
I see you want to use Unregistered
value (of type Account
) as the initial value and you also added a type annotation specifying that the return type of evolve
should be Account
. I think this is actually what you want. The only thing missing is that you need to wrap the returned values using the Registered
union case to turn RegisteredAccount
into Account
.
The following type-checks fine for me:
let evolve state event: Account =
match event with
| Created accountCreated ->
{ AccountId = accountCreated.AccountId
Owner = accountCreated.Owner
Balance = accountCreated.StartingBalance } |> Registered
| AccountDebited accountDebited ->
match state with
| Registered s ->
{ s with
Balance = s.Balance - accountDebited.Amount } |> Registered
| _ -> failwith "unregistered account can't be debited"
| _ -> failwith "todo: other cases"
All I had to do is to add |> Registered
in two places where you are returning a record!
Upvotes: 3