I have a type for logging levels:
type LoggingLevel =
| Trace
| Debug
| Info
I would like to say that some logging levels are higher than others. For example, Trace is higher than Info.
So I implemented IComparable like this:
[<StructuralEqualityAttribute>]
[<CustomComparisonAttribute>]
type LoggingLevel =
| Trace
| Debug
| Info
interface IComparable<LoggingLevel> with
override this.CompareTo other =
let score x =
match x with
| Trace -> 0
| Debug -> 1
| Info -> 2
(score this) - (score other)
But when I try to use it, I get an error:
if a >= b
then
// ...
The type 'LoggingLevel' does not support the 'comparison' constraint. For example, it does not support the 'System.IComparable' interface
How have I gone wrong here?
I managed to get it working, but now the type definition is so verbose! There must be a better way...
[<CustomEquality>]
[<CustomComparisonAttribute>]
type LoggingLevel =
| Trace
| Debug
| Info
override this.Equals (obj) =
match obj with
| :? LoggingLevel as other ->
match (this, other) with
| (Trace, Trace) -> true
| (Debug, Debug) -> true
| (Info, Info) -> true
| _ -> false
| _ -> false
override this.GetHashCode () =
match this with
| Trace -> 0
| Debug -> 1
| Info -> 2
interface IComparable<LoggingLevel> with
member this.CompareTo (other : LoggingLevel) =
let score x =
match x with
| Trace -> 0
| Debug -> 1
| Info -> 2
(score this) - (score other)
interface IComparable with
override this.CompareTo other =
(this :> IComparable<LoggingLevel>).CompareTo (other :?> LoggingLevel)
I would like to say that some logging levels are higher than others. For example, Trace is higher than Info.
Do you need to use custom equality and custom comparison at all? F# has these built in for Discriminated Unions. You just need to write them in increasing order in the type definition:
type LoggingLevel =
| Info
| Debug
| Trace // Note the order here! 🤔
Trace > Info // true
let levels = [ Trace; Debug; Info; Trace; Debug; Info ]
levels |> List.sort
// [Info; Info; Debug; Debug; Trace; Trace]
// Comparison ✔
levels |> List.countBy id
// [(Trace, 2); (Debug, 2); (Info, 2)]
// Equality ✔
More info: https://stackoverflow.com/a/52220335/1256041
I think you're comparison implementation is off here based on the typing. The following compiles for me:
[<CustomComparison>]
[<StructuralEquality>]
type LoggingLevel =
| Trace
| Debug
| Info
interface System.IComparable with
member this.CompareTo other =
0
// replace 0 with your comparison logic here
let a = Trace
let b = Debug
if Trace > Debug then printfn "here"
Note that other in this case will be of type obj and you'll need to box accordingly. Made all the trickier by the fact that all cases here are empty (i.e. lacking type)
I would be curious to see a more complete example, of you attempting to use this logic. I suspect a match expression might be better and allow you to remove this custom comparison.
That said, without knowing your exact use case, wouldn't something like this be more functional (and perhaps) simpler?
type LoggingLevel = Trace | Debug | Info
module Logger =
let doSomeLogging logLevel =
match logLevel with
| Trace -> "trace"
| Debug -> "debug"
| Info -> "info"
let result = Logger.doSomeLogging Trace
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 would like to do something like this:
let a x : Result<_, string> =
match x with
| 1 -> Ok
| _ -> Error "not 1"
this will not compile.
Is there a way to not specify an ok type and return nothing?
Not an experienced F# coder, but my understanding is that the way of indicating "no return value" is to use a Unit.
This compiles. Would it suffice for what you're doing?
let a x =
match x with
| 1 -> Ok ()
| _ -> Error "not 1"
Or with an explicit return type:
let a x : Result<Unit, string> =
match x with
| 1 -> Ok ()
| _ -> Error "not 1"
Given that Ok is a parameterized type, I don't think there's a way to get Ok by itself to work for what you're doing.
Suppose I have this type:
type T = int option
and an observable of that type:
let o : IObservable<T> = // create the observable
I'm looking for a better way to express this:
o.Where(function | None -> false | Some t -> true)
.Select(function | Some t -> t)
An observable that only propagates the Some case.
There are several things that I don't like.
I'm using 2 operators
I'm pattern matching twice
The second pattern matching isn't exhaustive (makes visual studio show a warning and feels odd)
Too much code. The pattern repeats every time I need pattern matching.
Can't you use Observable.choose ? something like this :
let o1 : IObservable<int option> = // ...
let o2 = Observable.choose id o1
If you have a type that is not an option, say:
type TwoSubcases<'a,'b> = | Case1 of 'a | Case2 of 'b
and a partial active pattern:
let (|SecondCase|_|) = function
| Case1 _ -> None
| Case2 b -> Some b
then you can do:
let o1 : IObservable<TwoSubcases<int, float>> = // ...
let o2 : IObservable<float> = Observable.choose (|SecondCase|_|) o1
Thanks to #Lee I came up with a nice solution.
o.SelectMany(function | None -> Observable.Empty() | Some t -> Observable.Return t)
This works for any union type, not only Option.
Here is my problem:
let foo =
match bar with
| barConfig1 -> configType1(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig2 -> configType2(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig3 -> configType3(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig4 -> configType4(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
I'd like to have the type of foo be determined by the match statement, but it always sets foo to the first type.
type bar =
|barConfig1
|barConfig2
|barConfig3
|barConfig4
In F#, there are no statements, only expressions, and each expression has to have a single concrete type. A match block is an expression as well, meaning that it has to have a single concrete type. What follows from that is that each case of the match has to have the same type as well.
That is, something like this is not valid F#:
let foo = // int? string?
match bar with // int? string?
| Int -> 3 // int
| String -> "Three" // string
In this case, the type inference mechanism will expect the type of the match to be the same as the type of the first case - int, and end up confused when it sees the string in the second. In your example the same thing happens - type inference expects all the cases to return a configType1.
A way around it would be by casting the values into a common supertype or interface type. So for your case, assuming the configTypes implement a common IConfigType interface:
let foo = // IConfigType
let arg = (devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
match bar with
| barConfig1 -> configType1(arg) :> IConfigType
| barConfig2 -> configType2(arg) :> IConfigType
| barConfig3 -> configType3(arg) :> IConfigType
| barConfig4 -> configType4(arg) :> IConfigType
If the output type has a limited number of cases, you can make that a discriminated union as well:
type ConfigType =
| ConfigType1 of configType1
| ConfigType2 of configType2
| ConfigType3 of configType3
| ConfigType4 of configType4``
let foo =
match bar with
| barConfig1 -> ConfigType1 <| configType1(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig2 -> ConfigType2 <| configType2(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig3 -> ConfigType3 <| configType3(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)
| barConfig4 -> ConfigType4 <| configType4(devices:DeviceEntities,DeviceStartIndex,inputStartIndex,outputStartIndex)``
Alternately, if they all implement an interface or inherit some base class, you can upcast to that, as with scrwtp's answer.
What would be the most effective way to express the following code?
match cond.EvalBool() with
| true ->
match body.Eval() with
| :? ControlFlowModifier as e ->
match e with
| Break(scope) -> e :> obj //Break is a DU element of ControlFlowModifier
| _ -> next() //other members of CFM should call next()
| _ -> next() //all other values should call next()
| false -> null
cond.EvalBool returns a boolean result where false should return null
and true should either run the entire block again (its wrapped in a func called next)
or if the special value of break is found, then the loop should exit and return the break value.
Is there any way to compress that block of code to something smaller?
I think that the code that you have written is fine. Here's an alternative which I marginally prefer:
let isBreak = function | Break(_) -> true | _ -> false
if cond.EvalBool() then
match body.Eval() with
| :? ControlFlowModifier as e when isBreak e -> e :> obj
| _ -> next()
else
null
I want to point out that it appears there's a subtype hierarchy for the result type of Eval, and if instead that were also a DU, then you could do something like
match body.Eval() with
| ControlFlowModifier(Break e) -> box e
| _ -> next()
Hurray for nested patterns.
I'm not too fond of matching booleans instead of using if-else. What about
let isBreak = function Break _ -> true | _ -> false
...
if cond.EvalBool() then
match body.Eval() with
| :? ControlFlowModifier as e when isBreak e -> box e
| _ -> next()
else null
Or, if you think that special isBreak function shouldn't be necessary (I'd understand that), lets try creating a more general function: C#'s as operator
let tryCast<'T> (o : obj) =
match o with
| :? 'T as x -> Some x
| _ -> None
...
if cond.EvalBool() then
match body.Eval() |> tryCast with
| Some (Break _ as e) -> box e //Break is a DU element of ControlFlowModifier
| _ -> next() //all other values should call next()
else null
I ended up creating an active pattern for this.
Similar logic exist elsewhere so I could make it reusable
let rec next() : obj =
if cond.EvalBool() then
match body.Eval() with
| IsBreak(res) -> res
| _ -> step.Eval() |> ignore ; next()
else null
Looks decent?
To flatten the nested match constructs, you'll need to use nested patterns. This works best for discriminated unions (as pointed out by Brian - and I agree that designing F# code to use primarily discriminated unions is the best thing you can do).
Otherwise, you'll need some active patterns if you want to write the code succinctly using match (ssp posted one example, which shows active patterns specifically for your problem). However, you can do this using the following two reusable active patterns:
let (|TryCast|_|) a : 'res option =
match (box a) with
| :? 'res as r -> Some(r)
| _ -> None
let (|Value|) (l:Lazy<_>) = l.Value
The first one is like :?, but it allows you to nest other patterns to match the value (which isn't possible with as). The second one forces evaluation of lazy value (I suppose that both of them could be declared in F# libraries as they are quite useful). Now you can write:
match lazy cond.EvalBool(), lazy body.Eval() with
| Value(true), Value(TryCast((Break(scope) : ControlFlowModifier)) as e) ->
e :> obj //Break is a DU element of ControlFlowModifier
| Value(true), _ ->
next() //all other values should call next()
| _, _ -> null
EDIT: As Roger pointed out in a comment, this version of the code may not be very readable. I think a better option would be to use only TryCast and format your original code slightly differently (although this isn't completely standard indentation, it is correct and F# compiler handles it fine):
match cond.EvalBool() with
| false -> null
| true ->
match body.Eval() with
| TryCast(Break(scope) as e) -> e :> obj
| _ -> next()
This is probably the most readable option based on pattern matching, but you could also use if instad of the first match as in the version by kvb and combine it with TryCast (this really depends on personal preferences):
if cond.EvalBool() then
match body.Eval() with
| TryCast(Break(scope) as e) -> e :> obj
| _ -> next()
else null
In any case, I believe that TryCast makes the code more readable as you avoid one nesting (which is othervise required because of :? .. as ..).
In case you mean "most effective way" as shortest code, i vote to AP too:
let (|CondEval|_|) (c,_) = if c.EvalBool() then Some true else None
let (|BodyEval|_|) (_,b) =
match b.Eval() with
| ControlFlowModifier as e -> Some e
| _ -> None
match cond,body with
| CondEval _ & BodyEval e -> e :> obj
| true -> next()
| false -> null