I have some types like this:
type Workshop = { Start: DateTime; End: DateTime }
type Mounting = { Start: DateTime; End: DateTime }
type FixedWorkTime = { Start: DateTime; End: DateTime }
type WorkTime =
| Workshop of Workshop
| Mounting of Mounting
type ApprovedWorkTime =
| ExistingWorkTime of WorkTime
| FixedWorkTime of FixedWorkTime
Now I want to create a function with this signature WorkTime -> ApprovedWorkTime
let FixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
match workTime with
| WorkTime.Workshop workshop -> ApprovedWorkTime.ExistingWorkTime { Start = workshop.Start, End = workshop.End }
| WorkTime.Mounting mounting -> ApprovedWorkTime.FixedWorkTime { Start = mounting.Start, End = mounting.End }
The match fails to compile with this error:
This expression was expected to have type 'WorkTime' but here has type 'FixedWorkTime'
How can I fix this?
Use semicolons, not commas, between the fields of the records. Whenever you see a comma, assume that means a tuple.
Second, ExistingWorkTime is of WorkTime, which is also a DU, so you have a hierarchy.
let fixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
match workTime with
| Workshop workshop -> ExistingWorkTime (Workshop { Start = workshop.Start; End = workshop.End })
| Mounting mounting -> FixedWorkTime { Start = mounting.Start; End = mounting.End }
The easy fix is to make the types match, as other answers already explained:
let FixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
match workTime with
| WorkTime.Workshop _ -> ExistingWorkTime workTime
| WorkTime.Mounting mounting -> FixedWorkTime { Start = mounting.Start; End = mounting.End }
However, I'd probably change your types a bit to make them more composable:
type Interval = { Start: DateTime; End: DateTime }
type WorkTime =
| Workshop of Interval
| Mounting of Interval
type ApprovedWorkTime =
| ExistingWorkTime of WorkTime
| FixedWorkTime of Interval
let FixWorkTime (workTime: WorkTime) : ApprovedWorkTime =
match workTime with
| Workshop _ -> ExistingWorkTime workTime
| Mounting interval -> FixedWorkTime interval
Here:
ApprovedWorkTime.ExistingWorkTime { Start = workshop.Start, End = workshop.End }
You're trying to construct a value of ApprovedWorkTime with the ExistingWorkTime constructor, and you're passing it a record as a parameter.
But according to its definition:
| ExistingWorkTime of WorkTime
It does not expect a record, or expects a value of type WorkTime.
So the shortest way to fix it would be to it workTime as a parameter:
ApprovedWorkTime.ExistingWorkTime workTime
Although I severely doubt that this is what you meant to do.
Separately I'd like to point out that your types are needlessly complicated. Look: every single value has the same fields, and they only differ in their constructors, and you have to shovel those fields back and forth every time you convert between values.
A more efficient model would have the Start and End fields at the top level, and the kind of work as a third field:
type WorkTime<'kind>= { Start: DateTime; End: DateTime; Kind: 'kind }
type WorkTimeKind = Workshop | Mounting
type ApprovedWorkTimeKind = ExistingWorkTime of WorkTime | FixedWorkTime
Related
type GenericResult =
| Ok
| Error of string
type LoginResult =
| Ok
| UserNotFound
| WrongPassword
let check something:GenericResult =
match something with
//| true -> Ok // error:This expression was expected to be of type "GenericREsult" but here has type "LoginResult"
| true -> GenericResult.Ok // I'm forced to specify GenericResult.Ok
| false -> Error "aargg!"
let checkLogin something:LoginResult =
match something with
| true -> Ok // here I don't need to specify the DU because this is defined after
| _ -> WrongPassword
I'd like to use just "Ok" in both the methods, without the need to specify the DU.
I see that in case of clashing of the value the last one is the "predefined".
Ideally I'd like to have a sort of inheritance
to reuse part of a DU in another DU.
For example:
type GenericResult =
| Ok
| Error of string
type LoginResult =
//| GenericResult.Ok
| UserNotFound
| WrongPassword
type SaveResult =
| Created
| Updated
//| GenericResult.Error
let checkLogin something: LoginResult | GenericResult.Ok =
match something with
| true -> Ok
| _ -> WrongPassword
[EDIT]
The real scenario where I feel the need for this feature is this with 3 different results from 3 different logic classes.
There will be in the future more cases so the multiplication of duplicated DU values will increase.
// DUs ordered from the most specific to the most generic
type BalanceUpdateResult =
| Created
| Updated
| InvalidRequest of string
type DeleteResult =
| Ok
| InvalidRequest of string
type Result<'T> =
| Ok of 'T
| NotValid of string
| Error of string
The goal is to have a clean match syntax in the consumer, where the value of the DU will evenctually be used to raise an exception or to return the created value, for example.
// balance update function (result is BalanceUpdateResult):
match result with
| Created -> this.createOkWithStatus 201
| Updated -> this.createOkWithStatus 200
| InvalidRequest error -> this.createErrorForConflict error
// company creation function (result is Result<Company>):
match result with
| Result.Ok newItem ->
context.Logger.Log $"Company created. New Id:{newItem.Id}, Name:{newItem.Name}."
this.createCreated newItem
| NotValid error -> base.createErrorForConflict error
| Error error -> base.createError error
Here, for example, InvalidRequest is not accepted in the second case because it belongs to the wrong DU.
Having to specify the DU everywhere results in a mess like the following example (see the many Result<_>.):
interface ICompanyLogic with
member this.Create(company:Company):Result<Company> =
match normalize company |> validate with
| NotValid msg -> Result<_>.NotValid msg
| Valid validCompany ->
match companyRepository.Exists(validCompany.Name) with
| true -> Result<_>.NotValid($"A company with name \"{validCompany.Name}\" already exists.")
| _ ->
let newCompany = assignNewId validCompany
companyRepository.Create(newCompany)
Result<_>.Ok(newCompany)
member this.Update (company:Company):Result<Company> =
let checkNameExists company =
match companyRepository.GetByName company.Name with
| Some c when c.Id <> company.Id -> NotValid $"A company with name \"{company.Name}\" already exists."
| _ -> Valid company
match normalize company |> validate with
| NotValid msg -> Result<_>.NotValid msg
| Valid c -> match checkNameExists c with
| Valid c -> companyRepository.Update c; Result<_>.Ok c
| NotValid msg -> Result<_>.NotValid msg
I think the best way to achieve what you are trying to do would be to start with a generic Result type that has a type parameter representing the error type:
type Result<'TError> =
| Ok
| Error of 'TError
This allows you to use different types for representing errors, including string, but also another DU to capture more specific error types. You can then define GenericResult and LoginResult as two type aliases:
type LoginError =
| UserNotFound
| WrongPassword
type GenericResult = Result<string>
type LoginResult = Result<LoginError>
To report a login error, you would now use Error WrongPassword to wrap the specific error in the generic Error constructor. The implementation of your two functions looks as follows:
let check something:GenericResult =
match something with
| true -> Ok
| false -> Error "aargg!"
let checkLogin something:LoginResult =
match something with
| true -> Ok
| _ -> Error WrongPassword
Unlike TypeScript union type, F# DU are meant to be composed and not extensible - see Thomas answer for a solution using this approach.
Since F# does not offer a direct solution, you may consider renaming cases like InvalidRequest in order to be more specific and to help differentiate them when reading the code. With these specific names, you can also merge all result types into a big Event DU like what's usually done in an event sourced system:
type Event =
// BalanceUpdateResult
| BalanceCreated
| BalanceUpdated
| BalanceUpdateError of string
// DeleteResult
| DeleteOk
| DeleteError of string
// ...
Ok, as explained by Romain multiple DUs cannot solve my problem.
I decided to use the built-in type Result<'T,'TError>.
It allows me to avoid create many DUs that inevitably will have clash of names, forcing the use the full DU prefix in the code.
I solved the problem that drove me to create custom DUs with the inspiring example from Thomas reply.
(with Result<,>) I have the possibility to have dinstinct Errors or Oks.
(note the Result<unit,_> and the Result<BalanceUpdateRequest,_>)
type ICompanyLogic =
abstract member Create:Company -> Result<Company, string> // CreateResult
abstract member Update:Company -> Result<Company, string> // UpdateResult
abstract member Delete:string -> Result<unit,string> // DeleteResult
type BalanceUpdateResult =
| Created
| Updated
type IBalanceLogic =
abstract member CreateOrUpdate: request:BalanceUpdateRequest -> Result<BalanceUpdateResult, string>
Apart BalanceUpdateResult all the other DUs where replaced buy the Result<'T,'TError>.
I just maintained a couple one for specific tasks:
type CompanyValidation = Valid of Company | NotValid of string
type ValidateResult = Valid | NotValid of string
In the end with this solution:
I don't need to define many DUs
I can customize the Result... within as many values I want (storing a sub-DU in the Ok or Error union case)
I don't need to use prefix or use synonims to avoid clash (code result much cleaner)
I have these types
type IntervalWithBreak = { Start: DateTime; End: DateTime }
type IntervalWithoutBreak = { Start: DateTime; End: DateTime; Break: TimeSpan }
type WorkTime =
| Workshop of IntervalWithBreak
| Mounting of IntervalWithBreak
| Ill of IntervalWithoutBreak
type WorktimeDefinition = {Weekday: DayOfWeek}
Now I want to create a function which allows me to find elements matching a condition.
Code completion for workTime in the matches function however doesn't provide the Start or End fields until I use this pattern match.
let matches (worktime:WorkTime) (worktimeDefinition:WorktimeDefinition) =
match worktime with
| Workshop w -> w.Start.DayOfWeek = worktimeDefinition.Weekday
| Mounting m -> m.Start.DayOfWeek = worktimeDefinition.Weekday
| Ill i -> i.Start.DayOfWeek = worktimeDefinition.Weekday
List.find (matches worktime) definitions
Can I have a more generic match so I don't have to check against all union cases but instead match against IntervalWithoutBreak and IntervalWithBreak?
My type definition is based on this suggestion.
As the argument of the Workshop case and the Mounting case is of the same type, the first thing you can do is to join them into an or-pattern:
let matches (worktime:WorkTime) (worktimeDefinition:WorktimeDefinition) =
match worktime with
| Workshop m
| Mounting m -> m.Start.DayOfWeek = worktimeDefinition.Weekday
| Ill i -> i.Start.DayOfWeek = worktimeDefinition.Weekday
This will not work for the Ill case, which contains an argument of a different type. But since they are all records, you can deconstruct the record in the pattern matching too:
let matches (worktime:WorkTime) (worktimeDefinition:WorktimeDefinition) =
match worktime with
| Workshop { Start = start }
| Mounting { Start = start }
| Ill { Start = start } -> start.DayOfWeek = worktimeDefinition.Weekday
As you now have just one case, you could actually write this using let, but I probably would not do this. I think match is more readable. But just for the record:
let matches (worktime:WorkTime) (worktimeDefinition:WorktimeDefinition) =
let (Workshop { Start = s } | Mounting { Start = s } | Ill { Start = s }) = worktime
s.DayOfWeek = worktimeDefinition.Weekday
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?
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!
I have a type:
type DictionaryCache<'a, 'b when 'a :comparison>()
And I have another type which contains some of this DictionaryCache:
type Cache() =
let user = new DictionaryCache<int, User>()
let userByLogin = new DictionaryCache<string, User>()
member this.User = user
member this.UserByLogin = userByLogin
In the last type I want to create generic function which will return one of the members based on input parameter:
member this.CacheNameToDictionary (cacheName: string) : DictionaryCache<'a, 'b> option =
match cacheName with
| "userByAutoincrementedId" -> Some(this.User)
| "userByLogin" -> Some(this.UserByLogin)
| _ -> None
But it doesn't work because of type mismatch.
Is there any way to rewrite this function ?
Update: here is a full code what I need to do:
type Cache() =
let user = new DictionaryCache<int, User>()
let userByLogin = new DictionaryCache<string, User>()
static let mutable instance = lazy(new Cache())
static member Instance with get() = instance.Value
member this.User = user
member this.UserByLogin = userByLogin
member this.Get (useCache: string) (cacheName: string) (id: 'a) longFunction exceptionFunction : 'b option =
let nameToDictionary() : DictionaryCache<'a, 'b> option =
match cacheName with
| "userByAutoincrementedId" -> Some(this.User)
| "userByLogin" -> Some(this.UserByLogin)
| _ -> None
let foo() : 'b option =
try
longFunction()
with
| exn -> exceptionFunction exn
None
match (useCache, nameToDictionary()) with
| "true", Some(dictionary) ->
match dictionary.Get id with
| Some(result) -> Some(result)
| _ -> match foo() with
| Some(result) -> dictionary.Put id result
Some(result)
| _ -> None
| _ -> foo()
This is not possible - the problem is that the return type of the method would depend on the string that it gets as the input argument. The input string is only known at run-time, but the type needs to be known at compile-time.
You could use the Choice type which lets you return one of multiple different types:
member this.CacheNameToDictionary (cacheName: string) =
match cacheName with
| "userByAutoincrementedId" -> Choice1Of3(this.User)
| "userByLogin" -> Choice2Of3(this.UserByLogin)
| _ -> Choice3Of3()
This works, but the return type lists all three alternatives and is pretty ugly:
Choice<DictionaryCache<int,User>, DictionaryCache<string,User>,unit>
Also, the consumer of this method will have to pattern match on the result and handle the two different dictionaries in different ways, so this might not make your code particularly beautiful.
Honestly, I think that you are adding a level of abstraction that you do not need. If there are two different keys, then you need different code to handle that and it's unlikely that you'll be able to write code that is extensible and adds third kind of dictionary.
I have the following code. For the last two match, the first period has type of DateTime option and the second one has type of int. Why the second one doesn't have option?
let (|Integer|_|) (str: string) =
let mutable intvalue = 0
if Int32.TryParse(str, &intvalue) then Some(intvalue)
else None
let (|DateyyMM|) (str: string) =
let mutable date = new DateTime()
if DateTime.TryParseExact(str,
"yyyyMM",
Globalization.DateTimeFormatInfo.InvariantInfo,
Globalization.DateTimeStyles.None,
&date)
then Some(date)
else None
let (|ParseRegex|_|) regex str =
let m = Regex(regex).Match(str)
if m.Success
then Some (List.tail [ for x in m.Groups -> x.Value ])
else None
.....
match url with
| ParseRegex "....." [DateyyMM period] -> //period type is DateTime option
......
match downloadLink.Url with
| ParseRegex "....." [name; Integer period] -> // period type is int
......
The second case has no option because you added _| at the end of the declaration.
This is setup to allow for a shorthand in a match - so that rather than
match x with
|Some_long_function(Some(res)) -> ...
|Some_long_function(None) -> ...
you can just do
match x with
|Some_long_function(res) -> ...
|_ -> ...
See the MSDN page on active patterns for more: http://msdn.microsoft.com/en-us/library/dd233248.aspx (in particular the secion on partial patterns)