I'm de-serializing some mappings from JSON and later on I need to pattern match based on a string field of the de-serialized types like this:
let mappings = getWorkItemMappings
let result =
workItemMappings
|> Seq.find (fun (m: WorkItemMapping) -> m.Uuid = workTime.workItemUuid)
match mapping.Name with
Even if I complete the pattern match for all cases I still get Incomplete pattern matches on this expression.. Which is obvious to me due to the string type of the Name field.
Is there a way tell the compiler which values for the Name field are available?.
I think I could create a union type for the possible mapping types and try to de-serialize the JSON to this union type but I would like to if there's another option.
If you are pattern matching on a string value, the compiler has no static guarantee that it will only have certain values, because it is always possible to construct a string of a different value. The fact that it comes from JSON does not help - you may always have an invalid JSON.
The best option is to add a default case which throws a custom descriptive exception. Either one that you handle somewhere else (to indicate that the JSON file was invalid) or (if you check the validity elsewhere) something like this:
let parseFood f =
match f with
| "burger" -> 1
| "pizza" -> 2
| _ -> raise(invalidArg "f" $"Expected burger or pizza but got {f}")
Note that the F# compiler is very cautious. It does not even let you handle enum values using pattern matching, because under the cover, there are ways of creating invalid enum values! For example:
type Foo =
| A = 1
let f (a:Foo) =
match a with
| Foo.A -> 0
warning FS0104: Enums may take values outside known cases. For example, the value 'enum (0)' may indicate a case not covered by the pattern(s).
Very hard to understand what you're asking. Maybe this snippet can be of help. It demos how literal string constants can be used in pattern matching, and reused in functions. This gives some added safety and readability when adding and removing cases. If you prefer not to serialize a DU directly, then perhaps this is useful as part of the solution.
type MyDu =
| A
| B
| C
let [<Literal>] A' = "A"
let [<Literal>] B' = "B"
let [<Literal>] C' = "C"
let strToMyDuOption (s: string) =
match s with
| A' -> Some A
| B' -> Some B
| C'-> Some C
| _ -> None
let strToMyDu (s: string) =
match s with
| A' -> A
| B' -> B
| C'-> C
| s -> failwith $"MyDu case {s} is unknown."
let myDuToStr (x: MyDu) =
match x with
| A -> A'
| B -> B'
| C -> C'
// LINQPad
let dump x = x.Dump()
strToMyDuOption A' |> dump
strToMyDuOption "x" |> dump
myDuToStr A |> dump
Related
I am trying to obtain a specific value from one list with a multiple recursion so I have :
type PowerSystem =
| System of string * int
| Junction of string * List<PowerSystem>
let Starship =
Junction("Core",
[
Junction("Users",
[
System("Main Computer",-10);
System("Library Computer",-10);
Junction("Defence",)]
let rec JunctionPath (pSystem:PowerSystem) =
match pSystem with
| Junction(name,aList) -> SplitList2 aList
| System(name,aNumber) -> [name]
and SplitList2 list =
match list with
| [] -> printfn "%A" []
| head::tail -> printfn "%A" List.filter (fun e->e="Port Phasers" (JunctionPath head)#(SplitList2 tail))
JunctionPath Starship
I get error FS0001: The type ''a -> string list' does not match the type 'unit'
I want to get the name of junctions when the system is called Main Computer but I can't call other functions. I tryed in different ways to get those values but I can't found the way. Thanks in advance
SplitList2 only has the side effect of printfn hence returns unit ().
In JunctionPath the first branch returns a list of string - [name] - and the second a unit due to calling SplitList2. This is not allowed as both (in general all) pattern match branches should return the same type. This is why you get your error "error FS0001: The type ''a -> string list' does not match the type 'unit'"
(It would be clearer if you, in future, note the line references in the error as that points to the problematic line).
Now in answer to your last point:
I want to get the name of junctions when the system is called Main
Computer but I can't call other functions. I tryed in different ways
to get those values but I can't found the way.
this does not make sense since what is "Port Phasers"? Anyway, without testing, I have modified SplitList2 to return a list on either branch. Then we pipe the result of JunctionPath into a printfn. However the logic of the line List.filter (fun e->e="Port Phasers" (JunctionPath head)#(SplitList2 tail)) still does not make sense, so I updated it as I think you meant it (although you should have had another error for this?). (This is not tail recursive and can be improved but one step at a time)
type PowerSystem =
| System of string * int
| Junction of string * List<PowerSystem>
let Starship =
Junction("Core",
[
Junction("Users",
[
System("Main Computer",-10);
System("Library Computer",-10);
Junction("Defence",)]
let rec JunctionPath (pSystem:PowerSystem) =
match pSystem with
| Junction(name,aList) -> SplitList2 aList
| System(name,aNumber) -> [name]
and SplitList2 list =
match list with
| [] -> []
| head::tail -> List.filter (fun e->e="Port Phasers") (JunctionPath head)#(SplitList2 tail))
JunctionPath Starship |> printfn "%A%
I created a nested Discriminated Union (DU) as follows:
type OptimizationPeriod = | All
| Long
| Short
type OptimizationCriterion = | SharpeRatio of OptimizationPeriod
| InformationRatio of OptimizationPeriod
| CalmarRatio of OptimizationPeriod
and also a non-nested DU:
type Parallelism = Sequential | PSeq
I have a JSON configuration file with strings that define the DU cases. The following function manages to identify the case of the non-nested Parallelism DU :
let stringToDUCase<'t> (name: string) : 't =
let dUCase =
Reflection.FSharpType.GetUnionCases( typeof<'t> )
|> Seq.tryFind (fun uc -> uc.Name = name)
|> Option.map (fun uc -> Reflection.FSharpValue.MakeUnion( uc, [||] ) :?> 't)
match dUCase with
| Some x -> x
| _ -> let msg = sprintf "config.json - %s is not a case in DU %A" name typeof<'t>
failwith msg
Note: I certainly copied it from somewhere as the function is a bit over my head, apologies to the author for not remembering where it came from.
Unfortunately this function fails to identify the case for the nested DU:
stringToDUCase<OptimizationCriterion> config.Trading.Criterion
System.Exception: config.json - SharpeRatio All is not a case in DU FractalTypes.OptimizationCriterion
Two questions:
1) I was able to write a function that deals specifically with the OptimizationCriterion DU and is able to identify the case. Is there a generic function along the lines of stringToDUCase that could do the same?
2) Would it be better to use a tuple of type OptimizationCriterion*OptimizationPeriod instead of a nested DU? (I probably would have to call stringToDUCase twice, but that is not a problem)
An "empty" DU case like All is just a value, but a "non-empty" DU case like SharpeRatio is actually a function that takes one value and returns the type. In this case, SharpeRatio has the type OptimizationPeriod -> OptimizationCriterion.
Your existing stringToDUCase function always passes an empty array into MakeUnion (implying an empty DU case). So here's a modified version of the function that works for any DU case:
let stringToParamDUCase<'t> (name: string) =
Reflection.FSharpType.GetUnionCases(typeof<'t>)
|> Seq.tryFind (fun uc -> uc.Name = name)
|> Option.map (fun uc ->
fun (parameters:obj []) -> Reflection.FSharpValue.MakeUnion(uc, parameters) :?> 't)
|> Option.defaultWith (fun () ->
failwith (sprintf "config.json - %s is not a case in DU %A" name typeof<'t>))
Note that it returns a function of obj [] -> 't. I've also simplified the error handling a little bit.
This is how you might use it:
let myOptimizationPeriod = stringToParamDUCase<OptimizationPeriod> "All" [||]
let f = stringToParamDUCase<OptimizationCriterion> "SharpeRatio"
let myOptimizationCriterion = f [|All|]
I think the existing answer should answer your question directly. However, I think it is worth making two additional points. First, it might be easier if you represented your OptimizationCriterion as a record, because all your DU cases contain the same value:
type OptimizationPeriod =
| All | Long | Short
type OptimizationRatio =
| SharpeRatio | InformationRatio | CalmanRatio
type OptimizationCriterion =
{ Ratio : OptimizationRatio
Period : OptimizationPeriod }
This happens to solve your problem too, because now you only need DUs without parameters, but I think it is also better design, because you avoid duplicating the second parameter.
Second, I don't think you really need to go with a fancy custom reflection-based function for deserialization. If you want to store your data in a JSON, you should either use standard library (Newtonsoft.JSON or Chiron will do just fine), or you can write this directly using something like JsonValue from F# Data, but using custom reflection code is a quick way leading to unmaintainable code.
With DU (Discriminated Union types), how do I perform a type test pattern matching ?
I have this following running code :
type IU =
|Int of int
|Unit of Unit
let x = IU.Int(3)
let y = IU.Unit(())
let z = [3.14]
let showI (v) =
match box v with
| :? IU ->
match v with
| Int(_) -> "an IU int"
|_ -> "not a IU.int"
|_ -> "not a IU.int"
But I am not happy with the inner match in the showI function. I would have preferred something like :
let showI (v) =
match box v with
| :? IU.Int -> "an int"
|_ -> "not a IU.int"
which doesn't compile (error : the type Int is not defined).
Is there an obvious syntax I missed ? Thanks.
Note : showI function accepts a variable with an unknowned type ; that is the reason for the smelly box v.
As others have pointed out, I don't think there's any built-in language feature that lets you do this. However, you could define an active pattern that performs the type test:
let (|IsIU|_|) (candidate : obj) =
match candidate with
| :? IU as iu -> Some iu
| _ -> None
This active pattern has the type obj -> IU option.
You can compose your own custom active pattern with standard patterns, like this:
let showI = function
| IsIU (IU.Int i) -> "an IU int"
| _ -> "not a IU.int"
In this example, the custom IsIU active pattern has been composed with a standard identifier pattern that matches on the IU.Int case.
Here's a sample FSI session showing usage with the x, y, and z values given in the OP:
> showI x;;
val it : string = "an IU int"
> showI y;;
val it : string = "not a IU.int"
> showI z;;
val it : string = "not a IU.int"
Staying within the context of your question I believe what you are missing is that IU.Int is not a type, but a case Int of discriminated union type IU. When you write
let x = IU.Int(3)
the type of value x is IU, not IU.Int. That's why compiler barks upon your attempt to match obj to UI.Int with :? pattern.
In a broader context, it seems you try approaching F# a-la dynamic language of Javascript kind, which it is not. Exaggerating a bit, you seemingly try using functions operating upon arguments of only one type obj and hence spending substantial run-time effort on dynamic discovery of specific argument types with wide opportunities for making mistakes on the way.
Such approach misses the whole point of F# idiomatic DU use case, which is disassembling of a value that is known to be statically typed as IU by pattern match machinery to specific union case (IU.Int or IU.Unit):
let showI (v : IU) = // explicit argument type is added to illuminate the point
match v with
| IU.Int(x) -> sprintf "a IU.Int(%i) value" x
| _ -> "a IU.Unit"
So, if you by mistake try calling showI with argument that is not of type IU, compiler will catch the erroneous use of your function with argument of wrong type right away and simply will not build the executable form of your code until the mistake is corrected.
EDIT: Idiomatic use aside you may get away with a single match, indeed, with the help of when guard, like in a snippet below, although this is a nasty hack:
open Microsoft.FSharp.Reflection
let showI (v) =
match box v with
| :? IU as x when (fst(FSharpValue.GetUnionFields(x, typeof<IU>))).Name.Equals("Int")
-> "an IU.Int"
| _ -> "not an IU.Int"
In F#, given
type MyType = A | B | C | D | E | F | G
How do I randomly define an instance of MyType?
This ought to work:
let randInst<'t>() =
let cases = Reflection.FSharpType.GetUnionCases(typeof<'t>)
let index = System.Random().Next(cases.Length)
let case = cases.[index]
Reflection.FSharpValue.MakeUnion(case, [||]) :?> 't
This code assumes that the union cases are all nullary and that the type you're using is actually a union type, but it would be easy to explicitly check those assumptions and throw meaningful exceptions if desired.
Select a random number, then pattern match that number with different branches returning a different instant?
I want to refine the raw text by using regular expression, given a list of (patten,replacement) tuple.
I tried to use the patten matching on the list element but failed, the error showed that "This expression was expected to have type string * string list but here has type 'a list".
How can I fix this problem? Thanks a lot.
Codes are as follows:
let rec refine (raw:string) (rules:string*string list) =
match rules with
| (pattern,replacement) :: rest ->
refine <| Regex.Replace(raw,pattern,replacement) rest
| [] -> raw
The problem is that a string * string list is a pair consisting of a string and a list of strings, whereas you want a (string * string) list:
let rec refine (raw:string) (rules:(string*string) list) =
match rules with
| (pattern,replacement) :: rest ->
refine (Regex.Replace(raw,pattern,replacement)) rest
| [] -> raw
Alternatively, the only reason you need that particular annotation is because Regex.Replace is overloaded. This is why your other solution works, but there are other (more minimal) places you can put an annotation that will work:
let rec refine (raw:string) rules =
match rules with
| (pattern,replacement:string) :: rest ->
refine (Regex.Replace(raw,pattern,replacement)) rest
| [] -> raw
Finally it works when I try this:
let rec refine (raw:string) rules =
match rules with
| rule :: rest ->
//get tuple values beyond the patten matching
let (pattern:string,replacement:string) = rule
refine (Regex.Replace(raw,pattern,replacement)) rest
| [] -> raw