I have an expression using pipe operator that converts the value to string and then to bool, however sometimes the original value can be null.
How can I use the pattern matching or something else to assume false when the value is null?
type kv = Dictionary<string, obj>
let allDayEvent (d: kv) = d.["fAllDayEvent"] |> string |> bool.Parse
There's quite a few places where you can safeguard via pattern matching: dictionary lookup, casting, parsing. Here's an example with all of those:
let allDayEvent (d: kv) =
match d.TryGetValue "fAllDayEvent" with
| true, v ->
match v with
| null -> printfn "null found"
| :? string as s ->
match bool.TryParse s with
| true, b -> printfn "found a bool: %A" b
| _ -> printfn "That's not a bool?"
| v -> printfn "Found something of type %s" (v.GetType().Name)
| _ -> printfn "No such key"
See also related questions, for example this.
Not sure why you are using a Dictionary, but I would probably have gone for a Map instead. Or at least done some Conversion to Map somewhere. And then I would maybe have thrown in some "automagically" handling of nulls.
And then Pandoras Box is kind of opened, but....
let (|Bool|) str =
match System.Boolean.TryParse(str) with
| (true,bool) -> Some(bool)
| _ -> None
let (|String|) (o:obj) =
match o with
| :? string as s -> Some(s)
| _ -> None
type kv = Dictionary<string, obj>
let allDayEvent (d: kv) =
d :> seq<_>
|> Seq.map (|KeyValue|)
|> Map.ofSeq
|> Map.tryFind "fAllDayEvent"
|> Option.bind (|String|)
|> Option.bind (|Bool|)
Note that allDayEvent in the above now is an Option, which maybe is in fact what you need/want.
And it does keep all data in place. Like true or false is not the same as "did not find stuff" or "could not convert stuff to some bool". Now it is in fact one of the following:
key found and some string like "true": Some(true)
key found and some string like "false": Some(false)
key not found or string not convertable to bool: None
Code is not tested and may need some further massaging.
Related
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
This seems like a question that has an ultra simple answer, but I can't think of it:
Is there a built in method, within Result, for:
let (a: Result<'a, 'a>) = ...
match a with
| Ok x -> x
| Error e -> e
No, because this function requires the Ok type and the Error type to be the same, which makes Result less general.
No, there isn't any function which will allow you to do so. But you can easily define it:
[<RequireQualifiedAccess>]
module Result =
let join (value: Result<'a, 'a>) =
match value with
| Ok v -> v
| Error e -> e
let getResult s =
if System.String.IsNullOrEmpty s then
Error s
else
Ok s
let a =
getResult "asd"
|> Result.join
|> printfn "%s"
It doesn't make Result less general (as said by #brianberns), because it's not an instance member. Existence of Unwrap doesn't make Task less general
Update
After more scrupulous searching inside FSharpPlus and FSharpx.Extras I've found necessary function. It's signature ('a -> 'c) -> ('b -> 'c) -> Result<'a,'b> -> c instead of Result<'a, 'a> -> 'a and it's called Result.either in both libraries (source 1 and source 2). So in order to get value we may pass id as both parameters:
#r "nuget:FSharpPlus"
open FSharpPlus
// OR
#r "nuget:FSharpx.Extras"
open FSharpx
getResult "asd"
|> Result.either id id
|> printfn "%s"
Also it's may be useful to define shortcut and call it Result.join or Result.fromEither as it's called in Haskell
I have the following f# code
product.code <- productPage.Html
.Descendants["li"]
.Select(fun node -> node.InnerText())
.Where(fun link -> (Regex.Match(link,#"code:").Success))
.FirstOrDefault()
.Replace("code:", "")
.Trim()
I'm having some trouble with nulls.
In c# I would do something like this.
product.code = productPage?.Html
?.Descendants["li"]
?.Select(node => node.InnerText())
?.Where(link => Regex.Match(link,#"code:").Success)
?.FirstOrDefault()
?.Replace("code:", "")
?.Trim() ?? "Not Found"
Is this possible?
In the second example, it looks to me like "?." has to be carried through the whole call chain due to its initial use. Rather than try to recreate this operator and preserve how this looks in C#, I suggest you go for more idiomatic F#. For example:
module String =
let replace (oldValue: string) (newValue: string) (s: string) =
s.Replace (oldValue, newValue)
let trim (s: string) =
s.Trim()
let result =
match isNull productPage with
| true -> None
| false ->
productPage.Html.Descendants.["li"]
|> Seq.map (fun node -> node.InnerText())
|> Seq.tryPick (fun link -> (Regex.Match (link, "code:").Success))
let code =
match result with
| Some html ->
html
|> String.replace "code:" ""
|> String.trim
| None -> "Not Found"
product.code <- code
I have a situation in finding a sequence of strings that have patterns like XXXX or CCCC or "IIII". I have tried the following code, but it does not work
let rec checkSequence roman=
let r=List.ofSeq roman
match r with
| [] -> true
| a::b::c::d::tail when (a="I" || a="X" || a="C") && a=b && a=c && a=d -> false
| head::tail -> checkSequence tail
checkSequence "CCC"
The error is: This expression was expected to have type string list but here has type string
1-How can I resolve this error?
2-Is there any simpler way to find this patterns?
If you need use a recursion on list you may do something like this:
let checkSequenceStr str =
let rec checkSequence roman =
match roman with
| [] -> true
| 'I'::'I'::'I'::'I'::tail -> false
| 'X'::'X'::'X'::'X'::tail -> false
| 'C'::'C'::'C'::'C'::tail -> false
| head::tail -> checkSequence tail
checkSequence (str |> List.ofSeq)
Or you could use .NET string methods to check patterns directly (which is easier):
let checkPattern (str : string) =
["IIII";"CCCC";"XXXX"] |> List.exists str.Contains |> not
You are using List.ofSeq, this will type force the roman parameter to be of an type list.
https://msdn.microsoft.com/en-us/library/ee340325.aspx
Therefor your error This expression was expected to have type string list but here has type string is due to calling the function wrongly, then an logical error. Therefor change:
checkSequence "CCC"
Into:
checkSequence ["C"; "C";"C"]
I continue to work on a printer for F# quoted expressions, it doesn't have to be perfect, but I'd like to see what is possible. The active patterns in Microsoft.FSharp.Quotations.Patterns and Microsoft.FSharp.Quotations.DerivedPatterns used for decomposing quoted expressions will typically provide MemberInfo instances when appropriate, these can be used to obtain the name of a property, function, etc. and their "declaring" type, such as a module or static class. The problem is, I only know how to obtain the CompiledName from these instances but I'd like the F# name. For example,
> <# List.mapi (fun i j -> i+j) [1;2;3] #> |> (function Call(_,mi,_) -> mi.DeclaringType.Name, mi.Name);;
val it : string * string = ("ListModule", "MapIndexed")
How can this match be rewritten to return ("List", "mapi")? Is it possible?
FYI, here is my final polished solution from Stringer Bell and pblasucci's help:
let moduleSourceName (declaringType:Type) =
FSharpEntity.FromType(declaringType).DisplayName
let methodSourceName (mi:MemberInfo) =
mi.GetCustomAttributes(true)
|> Array.tryPick
(function
| :? CompilationSourceNameAttribute as csna -> Some(csna)
| _ -> None)
|> (function | Some(csna) -> csna.SourceName | None -> mi.Name)
//usage:
let sourceNames =
<# List.mapi (fun i j -> i+j) [1;2;3] #>
|> (function Call(_,mi,_) -> mi.DeclaringType |> moduleSourceName, mi |> methodSourceName);
You can use F# powerpack for that purpose:
open Microsoft.FSharp.Metadata
...
| Call(_, mi, _) ->
let ty = Microsoft.FSharp.Metadata.FSharpEntity.FromType(mi.DeclaringType)
let name = ty.DisplayName // name is List
However, I don't think if it's possible to retrieve function name with powerpack.
Edit:
As hinted by pblasucci, you can use CompilationSourceName attribute for retrieving source name:
let infos = mi.DeclaringType.GetMember(mi.Name)
let att = infos.[0].GetCustomAttributes(true)
let fName =
(att.[1] :?> CompilationSourceNameAttribute).SourceName // fName is mapi