I have the following function that checks the existance of a customer in a data source and returns the id. Is this the right/idiomatic way of using the Option type?
let findCustomerId fname lname email =
let (==) (a:string) (b:string) = a.ToLower() = b.ToLower()
let validFName name (cus:customer) = name == cus.firstname
let validLName name (cus:customer) = name == cus.lastname
let validEmail email (cus:customer) = email == cus.email
let allCustomers = Data.Customers()
let tryFind pred = allCustomers |> Seq.tryFind pred
tryFind (fun cus -> validFName fname cus && validEmail email cus && validLName lname cus)
|> function
| Some cus -> cus.id
| None -> tryFind (fun cus -> validFName fname cus && validEmail email cus)
|> function
| Some cus -> cus.id
| None -> tryFind (fun cus -> validEmail email cus)
|> function
| Some cus -> cus.id
| None -> createGuest() |> fun cus -> cus.id
It's never good when you have indent upon indent, so it'd be worthwhile seeing what you can do about it.
Here's one way to address the problem, by introducing a little helper function:
let tryFindNext pred = function
| Some x -> Some x
| None -> tryFind pred
You can use it inside the findCustomerId function to flatten the fallback options:
let findCustomerId' fname lname email =
let (==) (a:string) (b:string) = a.ToLower() = b.ToLower()
let validFName name (cus:customer) = name == cus.firstname
let validLName name (cus:customer) = name == cus.lastname
let validEmail email (cus:customer) = email == cus.email
let allCustomers = Data.Customers()
let tryFind pred = allCustomers |> Seq.tryFind pred
let tryFindNext pred = function
| Some x -> Some x
| None -> tryFind pred
tryFind (fun cus -> validFName fname cus && validEmail email cus && validLName lname cus)
|> tryFindNext (fun cus -> validFName fname cus && validEmail email cus)
|> tryFindNext (fun cus -> validEmail email cus)
|> function | Some cus -> cus.id | None -> createGuest().id
This is very similar to the approach outlined here.
Options form a monad and they are also monoidal in that they support two functions of the form
zero: Option<T>
combine: Option<T> -> Option<T> -> Option<T>
computation expressions are used to provide a nicer way of working with monads and they also support the monoid operations. You can therefore implement a computation builder for Option:
type OptionBuilder() =
member this.Return(x) = Some(x)
member this.ReturnFrom(o: Option<_>) = o
member this.Bind(o, f) =
match o with
| None -> None
| Some(x) -> f x
member this.Delay(f) = f()
member this.Yield(x) = Some(x)
member this.YieldFrom(o: Option<_>) = o
member this.Zero() = None
member this.Combine(x, y) =
match x with
| None -> y
| _ -> x
let maybe = OptionBuilder()
where Combine returns the first non-empty Option value. You can then use this to implement your function:
let existing = maybe {
yield! tryFind (fun cus -> validFName fname cus && validEmail email cus && validLName lname cus)
yield! tryFind (fun cus -> validFName fname cus && validEmail email cus)
yield! tryFind (fun cus -> validEmail email cus)
}
match existing with
| Some(c) -> c.id
| None -> (createGuest()).id
A little abstraction can go a long way in terms of readability...
let bindNone binder opt = if Option.isSome opt then opt else binder ()
let findCustomerId fname lname email =
let allCustomers = Data.Customers ()
let (==) (a:string) (b:string) = a.ToLower () = b.ToLower ()
let validFName name (cus:customer) = name == cus.firstname
let validLName name (cus:customer) = name == cus.lastname
let validEmail email (cus:customer) = email == cus.email
let tryFind pred = allCustomers |> Seq.tryFind pred
tryFind (fun cus -> validFName fname cus && validEmail email cus && validLName lname cus)
|> bindNone (fun () -> tryFind (fun cus -> validFName fname cus && validEmail email cus))
|> bindNone (fun () -> tryFind (fun cus -> validEmail email cus))
|> bindNone (fun () -> Some (createGuest ()))
|> Option.get
|> fun cus -> cus.id
Much easier to follow, and the only overhead is a few extra null checks.
Also, if I were you, because most of these functions are so small/trivial, I would sprinkle inline around judiciously.
First, this may not be directly related to your question, but you might want to rearrage the logic in this function.
Instead of:
"I look for a customer that matches fname, lastname, and emai; failing that, I look for just fname + email, then just email, then create a guest"
it may be better to proceed like this:
"I look for a matching email. If I get multiple matches, I look for a matching fname, and if there's multiples again I look for a matching lname".
This would not just enable you to structure your code better, but force you to deal with possible problems in the logic.
For example, what if you have multiple matching emails, but none of them have the correct name? Currently you simply pick the first in the sequence, which may or may not be what you want, depending on how Data.Customers() is ordered, if it is ordered.
Now, if emails must be unique then this won't be an issue - but if that were the case then you might as well skip checking the first/last names!
(I hesitate to mention it, but it might also speed up your code somewhat, as you don't needlessly check records more than once for the same fields, nor do you check for additional fields when just the email suffices.)
And getting to your question now - the problem isn't in the use of Option, the problem is that you're performing essentially the same operation three times! ("Find matches, then if not found look for a fallback"). Refactoring the function in a recursive fashion will eliminate the ugly diagonal structure, and allows you to trivially extend the function in the future to check for additional fields.
Some other minor suggestions for your code:
Since you only ever invoke the validFoo helper functions with the same arguments for Foo, you can bake them into the function definitions to slim up the code.
Using .toLower()/.toUpper() for case-insensitive string comparison is common, but slightly suboptimal as it actually creates new lower-case copies of each string. The proper way is to use String.Equals(a, b, StringComparison.CurrentCultureIgnoreCase). 99% of the time this is an irrelevant micro-optimizazion, but if you have a huge customer database and do lots of customer lookups, this is the sort of function where it might actually matter!
If it's possible, I would modify the createGuest function so that it returns the whole customer object, and only take the .id as the very last line of this function - or better yet, return a customer from this function as well, and offer a separate one-liner findCustomerId = findCustomer >> (fun c -> c.id) for ease of use.
With all of that, we have the following. For the sake of the example, I will assume that in the case of multiple equally valid matches, you will want the last, or most recent one. But you could also throw an exception, sort by a date field, or whatever.
let findCustomerId fname lname email =
let (==) (a:string) (b:string) = String.Equals(a, b, StringComparison.CurrentCultureIgnoreCase)
let validFName = fun (cus:customer) -> fname == cus.firstname
let validLName = fun (cus:customer) -> lname == cus.lastname
let validEmail = fun (cus:customer) -> email == cus.email
let allCustomers = Data.Customers ()
let pickBetweenEquallyValid = Seq.last
let rec check customers predicates fallback =
match predicates with
| [] -> fallback
| pred :: otherPreds ->
let matchingCustomers = customers |> Seq.filter pred
match Seq.length matchingCustomers with
| 0 -> fallback
| 1 -> (Seq.head matchingCustomers).id
| _ -> check matchingCustomers otherPreds (pickBetweenEquallyValid matchingCustomers).id
check allCustomers [validEmail; validFName; validLName] (createGuest())
One last thing: those ugly (and often O(n)) Seq.foo expressions everywhere are necessary because I don't know what kind of sequence Data.Customers returns, and the general Seq class isn't very friendly to pattern-matching.
If, for example, Data.Customers returns an array, then the readability would be improved significantly:
let pickBetweenEquallyValid results = results.[results.Length - 1]
let rec check customers predicates fallback =
match predicates with
| [] -> fallback
| pred :: otherPreds ->
let matchingCustomers = customers |> Array.filter pred
match matchingCustomers with
| [||] -> fallback
| [| uniqueMatch |] -> uniqueMatch.id
| _ -> check matchingCustomers otherPreds (pickBetweenEquallyValid matchingCustomers).id
check allCustomers [validEmail; validFName; validLName] (createGuest())
Talking of idiomatic use of language, first and foremost F# promotes writing terse code that clearly reflects intent. When looking at your snippet from this standpoint most of code there is excessive and only hides the observation that the returned value does not anyhow depend upon either firstname or lastname.
Your snippet may be refactored to much shorter and much clearer equivalent function that:
being given three arguments ignores all, but email,
then of sequence of all customers tries to find one having (ignoring case) the same email,
if such found, then returns its id, othervise returns createGuest().id
which almost literally translates into
let findCustomerId _ _ email =
Data.Customers()
|> Seq.tryFind (fun c -> System.String.Compare(email,c.email,true) = 0)
|> function Some(c) -> c.id | None -> createGuest().id
Let me rephrase and amend the problem statement:
I'm looking for 1) matching first name, last name and email in which case I'd like to terminate the iteration.
Failing that, I temporarily store a customer with 2) matching first name and email or, less preferably, 3) only a matching email, and continue looking for 1).
The elements of the sequence should be evaluated at most once.
This kind of problem is not very amenable to pipelined Seq functions, as it involves state in an escalating hierarchy, with termination when the highest state is reached.
So let's do it in an imperative way, making the state mutable, but using a discriminated union to encode it and with pattern matching to effect the state transitions.
type MatchType<'a> =
| AllFields of 'a
| FNameEmail of 'a
| Email of 'a
| NoMatch
let findCustomerId fname lname email =
let allCustomers = Data.Customers ()
let (==) a b = // Needs tweaking to pass the Turkey Test
System.String.Equals(a, b, System.StringComparison.CurrentCultureIgnoreCase)
let notAllFields = function AllFields _ -> false | _ -> true
let state = ref NoMatch
use en = allCustomers.GetEnumerator()
while notAllFields !state && en.MoveNext() do
let cus = en.Current
let fn = fname == cus.firstname
let ln = lname == cus.lastname
let em = email == cus.email
match !state with
| _ when fn && ln && em -> state := AllFields cus
| Email _ | NoMatch when fn && em -> state := FNameEmail cus
| NoMatch when em -> state := Email cus
| _ -> ()
match !state with
| AllFields cus
| FNameEmail cus
| Email cus -> cus.id
| NoMatch -> createGuest().id
Related
F# 6.0.3
I have seen some solutions on Google that are close to what I need; but being a Newbie I can't quite get how to use bind and map to get the solution.
I have many working procedures of the following format:
Example #1:
let saveAllDiagnosis =
let savealldiagnosis = match m.Encounter with
| None -> failwith "No encounter found"
| Some e -> match e.EncounterId with
| None -> failwith "No Encounter id found"
| Some id -> m.AllDiagnosisList
|> List.iter ( fun dx -> match dx.Key with
| None -> ()
| Some k -> Async.RunSynchronously (editAllDiagnosisInPreviousEncountersAsync id dx))
savealldiagnosis
Example #2
let saveEncounterDiagnosis =
let savedx = match m.Encounter with
| None -> failwith "No encounter found"
| Some e -> match e.EncounterId with
| None -> failwith "No Encounter id found"
| Some id -> m.BillingDiagnosisList |> List.iter ( fun dx -> Async.RunSynchronously (saveDxAsync id dx))
savedx
As can be seen, these are nested methods with almost identical behavior--differing only in the async procedure being called and the initializing list. What I would like to do is something along the lines of:
let runProcedures (fn: Model->Async) Model = ????
That is, a single procedue that encapsulates everything except the Async method and it's parameters but manages all the "None"s in a better way.
I hope my intent is clear.
TIA
If you are happy with using exceptions, then you do not even need railway-oriented programming (ROP). ROP is useful for more complex validation tasks, but I think exceptions are often perfectly reasonable and easy way of handling errors. In your case, you could define a helper that extracts a value of option<'T> or fails with a given error message:
let orFailWith msg opt =
match opt with
| Some v -> v
| None -> failwithf "%s" msg
Using this, you can then rewrite your code as follows:
let saveAllDiagnosis =
let e = m.Encounter |> orFailWith "No encounter found"
let id = e.EncounterId |> orFailWith "No Encounter id found"
for dx in m.AllDiagnosisList do
dx.Key |> Option.iter (fun k ->
editAllDiagnosisInPreviousEncountersAsync id dx |> Async.RunSynchronously)
let saveEncounterDiagnosis =
let e = m.Encounter |> orFailWith "No encounter found"
let id = e.EncounterId |> orFailWith "No Encounter id found"
for dx in m.BillingDiagnosisList do
saveDxAsync id dx |> Async.RunSynchronously
As I do not know the broader context of this, it is hard to say more - your code is imperative, but that may be perfectly fine if you are following the sandwich pattern.
Using mentioned ROP code can be rewritten as such. Result is used to track error and throw it at the end of pipeline. With current design is possible to avoid exceptions by just logging error instead of throwing at before last line.
type Encounter = { EncounterId : int option }
type Diagnostic = { Key : int option }
type Thing = {
Encounter : Encounter option
AllDiagnosisList : Diagnostic list
}
let editAllDiagnosisInPreviousEncountersAsync id diag = async { return () }
module Result =
let ofOption err opt =
match opt with
| Some v -> Ok v
| None -> Error err
let join res =
match res with
| Error v
| Ok v -> v
let saveAllDiagnosis m =
m.Encounter
|> Result.ofOption "No encounter found" // get value from option or log error
|> Result.map (fun e -> e.EncounterId)
|> Result.bind (Result.ofOption "No Encounter id found") // get EncounterId or log error
|> Result.map (fun id -> (
m.AllDiagnosisList
|> Seq.where (fun dx -> dx.Key.IsSome)
|> Seq.iter (fun dx -> Async.RunSynchronously (editAllDiagnosisInPreviousEncountersAsync id dx))
))
|> Result.mapError failwith // throw error
|> Result.join // Convert Result<unit, unit> into unit
The solutions posted above are very helpful to this newbie. But adding my own two cents worth, I going with this:
let _deleteDxFromEncounterAsync (encounterId:int) (dx:Diagnosis) : Async<unit> = deleteDxFromEncounterAsync encounterId dx.Description
let _deleteDxFromAllPreviousEncountersAsync (encounterId:int) (dx:Diagnosis) : Async<unit> = deleteDxFromAllPreviousEncountersAsync encounterId dx.Description
let _saveDxAsync (encounterId:int) (dx:Diagnosis) : Async<unit> = saveDxAsync encounterId dx
let _editAllDiagnosisInPreviousEncountersAsync (encounterId:int) (dx:Diagnosis) : Async<unit> = editAllDiagnosisInPreviousEncountersAsync encounterId dx
let listchk (dxs:Diagnosis list) : Diagnosis list option =
match dxs with
| [] -> None
| _ -> Some dxs
let _save (fn:int -> Diagnosis-> Async<unit>) (dxs:Diagnosis list) : unit =
match dxs |> listchk, m.Encounter |> Option.bind (fun v -> v.EncounterId) with
| Some dxs, Some id -> dxs |> List.iter (fun dx -> Async.RunSynchronously(fn id dx))
| _,_ -> failwith "Missing Encounter or EncounterId or Empty List"
m.DeletedBillingDiagnosis |>_save _deleteDxFromEncounterAsync
m.DeletedAllDiagnosis |>_save _deleteDxFromAllPreviousEncountersAsync
m.BillingDiagnosisList |>_save _saveDxAsync
m.AllDiagnosisList |> List.filter (fun dx -> dx.Key.IsSome) |>_save _editAllDiagnosisInPreviousEncountersAsync
For speed, in the future, I will probably have the Async functions act on the entire list at one time rather then one item; but for now, this code comes closest to my intent in asking the question. IMPROVEMENTS AND CRITISM IS GLADDLY APPRECIATED! F# is fun!
Thanks to all.
I have a series of validation functions I want to put into an array to execute:
type result = {D: int; E: int; F: int; G: int}
type InvalidReason =
| AAA
| BBB
| CCC
| DDD
| EEE
type Validation =
| Valid
| Invalid of InvalidReason
let validators = [|AAA; BBB; CCC; DDD; EEE|]
let validateStuff result =
validators
|> Array.map(fun v -> v result)
|> Array.contains(Validation.Invalid _)
The problem is that last line of code. I am getting an "Unexpected value _ in the expression." The following does work
|> Array.contains(Validation.Valid)
|> Array.contains(Validation.Invalid InvalidReason.AAA)
But I don't want to spell out each of the sub types for InvalidReasons. Is there some syntax I am overlooking?
The function Array.contains takes a value and checks if that value is in the array. What you're trying to do is to give it a whole bunch of values to check. Well, this won't work: the function only takes one. And it doesn't help that there is no syntax like that in F# :-)
You might use another function that takes multiple values, but a better way to accomplish what you want is to use a function that takes a predicate - Array.exists. Make yourself a predicate to check if a value is "invalid":
let isInvalid x = match x with
| Valid -> false
| Invalid _ -> true
And pass it to Array.exists:
let validateStuff result =
validators
|> Array.map(fun v -> v result)
|> Array.exists isInvalid
Or you could even put that function inline:
let validateStuff result =
validators
|> Array.map(fun v -> v result)
|> Array.exists ( fun x -> match x with
| Valid -> false
| Invalid _ -> true )
Or even shorter, using the function keyword:
let validateStuff result =
validators
|> Array.map(fun v -> v result)
|> Array.exists ( function | Valid -> false | Invalid _ -> true )
Or even shorter, getting rid of as much noise as possible:
let validateStuff result =
validators
|> Array.map(fun v -> v result)
|> Array.exists ( function Invalid _ -> true | _ -> false )
I have a function that pattern matches its argument, which is a string:
let processLexime lexime
match lexime with
| "abc" -> ...
| "bar" -> ...
| "cat" -> ...
| _ -> ...
This works as expected. However, I'm now trying to extend this by expressing "match a string containing only the following characters". In my specific example, I want anything containing only digits to be matched.
My question is, how can I express this in F#? I'd prefer to do this without any libraries such as FParsec, since I'm mainly doing this for learning purposes.
You can use active patterns: https://msdn.microsoft.com/en-us/library/dd233248.aspx
let (|Integer|_|) (str: string) =
let mutable intvalue = 0
if System.Int32.TryParse(str, &intvalue) then Some(intvalue)
else None
let parseNumeric str =
match str with
| Integer i -> printfn "%d : Integer" i
| _ -> printfn "%s : Not matched." str
One way would be an active pattern
let (|Digits|_|) (s:string) =
s.ToCharArray() |> Array.forall (fun c -> System.Char.IsDigit(c)) |> function |true -> Some(s) |false -> None
then you can do
match "1" with
|Digits(t) -> printf "matched"
I would use regular expressions combined with active patterns. With regular expressions you can easily match digits with \d and active patterns makes the syntax nice inside your match.
open System.Text.RegularExpressions
let (|ParseRegex|_|) regex str =
let m = Regex("^"+regex+"$").Match(str)
if (m.Success) then Some true else None
let Printmatch s =
match s with
| ParseRegex "w+" d -> printfn "only w"
| ParseRegex "(w+|s+)+" d -> printfn "only w and s"
| ParseRegex "\d+" d -> printfn "only digis"
|_ -> printfn "wrong"
[<EntryPoint>]
let main argv =
Printmatch "www"
Printmatch "ssswwswwws"
Printmatch "134554"
Printmatch "1dwd3ddwwd"
0
which prints
only w
only w and s
only digis
wrong
I'm trying to create DU cases from strings. The only way I can see doing this is by enumerating over the DU cases via Microsoft.FSharp.Reflection.FSharpType.GetUnionCases and then picking the UnionCase that matches the string (by using .Name) and then making the actual DU case out of that by using FSharpValue.MakeUnion.
Isn't there an easier/more elegant way of doing this? In my scenario I have a DU with a couple of hundred cases for keywords. I have to read the strings (keywords) from a file and make the types out of them. I did some "optimization" by putting the cases into a Map but I was hoping there'd be a better way of doing this.
I have the following, for example:
type Keyword =
| FOO
| BAR
| BAZ
| BLAH
let mkKeywords (file: string) =
use sr = new StreamReader(file)
let caseMap =
FSharpType.GetUnionCases(typeof<Keyword>)
|> Array.map (fun c -> (c.Name, FSharpValue.MakeUnion(c, [||]) :?> Keyword))
|> Map.ofArray
[
while not sr.EndOfStream do
let l = sr.ReadLine().Trim()
match caseMap.TryFind l with
| Some c -> yield c
| None -> failwith <| "Could not find keyword: " + l
]
I found this handy code snippet...
open Microsoft.FSharp.Reflection
let toString (x:'a) =
let (case, _ ) = FSharpValue.GetUnionFields(x, typeof<'a>)
case.Name
let fromString<'a> (s:string) =
match FSharpType.GetUnionCases typeof<'a> |> Array.filter (fun case -> case.Name = s) with
|[|case|] -> Some(FSharpValue.MakeUnion(case,[||]) :?> 'a)
|_ -> None
... which makes it easy to tack on two lines of code to any DU...
type A = X|Y|Z with
override this.ToString() = FSharpUtils.toString this
static member fromString s = FSharpUtils.fromString<A> s
I would use pattern matching like this:
type Keyword =
| FOO
| BAR
| BAZ
| BLAH
let matchKeyword (word:string) : Keyword option =
match word with
| "FOO" -> Some FOO
| "BAR" -> Some BAR
| "BAZ" -> Some BAZ
| "BLAH" -> Some BLAH
| _ -> None
And maybe auto generate the match statement first time using regex in my editor, but only because you have hundreds of cases. But i am not sure if its a better solution then yours.
As the cases have no value, another option is to use enums:
type Keyword =
| FOO = 0
| BAR = 1
| BAZ = 2
| BLAH = 3
let strings = ["FOO";"BAR"]
let keywords =
[for s in strings -> s, Keyword.Parse(typeof<Keyword>, s)]
|> Map.ofList
Then you can simply use Enum.Parse.
type ProcessAttachmentResult = ValidAttachment | InvalidAttachment
let processAttachment ( attachment : Attachment ) =
if attachment.Name ="test.txt" then
printfn "%s valid"
ValidAttachment
else
printfn "%s invalid" attachment.Name
InvalidAttachment
// attachments is of type List<Attachment>
let processedAttachments = attachments |> List.map processAttachment
// ProcessAttachmentResult list
let emailContainsValidAttachments =
List.exists ( fun r -> r = ValidAttachment) processedAttachments
match emailContainsValidAttachments with
| true -> move email toProcessedFolder
| _ -> move email toErrorFolder
How can i change the last two let bindings and match to a single binding?
i tried
attachments |> List.map processAttachment |> List.exists (fun r -> r = ValidAttachment)
but this gives:
This expression was expected to have type ProcessAttachmentResult list but here has type bool
As pad mentioned in a comment, there is nothing wrong with your approach. You must have accidentally redefined some built-in function (like List.exists). To check this, try opening a new F# Script File and paste the following code.
It is essentially your code with the missing declarations added and it type-checks just fine:
type ProcessAttachmentResult = ValidAttachment | InvalidAttachment
type Attachment = { Name : string }
let attachments = []
let move a b = ()
let email = 0
let toProcessedFolder = ""
let toErrorFolder = ""
let processAttachment ( attachment : Attachment ) =
if attachment.Name = "test.txt" then
printfn "%s valid" // TOMAS: Minor issue here - you missed the argument
ValidAttachment
else
printfn "%s invalid" attachment.Name
InvalidAttachment
// attachments is of type List<Attachment>
let processedAttachments = attachments |> List.map processAttachment
// ProcessAttachmentResult list
let emailContainsValidAttachments =
List.exists ( fun r -> r = ValidAttachment) processedAttachments
match emailContainsValidAttachments with
| true -> move email toProcessedFolder
| _ -> move email toErrorFolder
// TOMAS: No problem here - this type-checks without errors
attachments
|> List.map processAttachment
|> List.exists ( fun r -> r = ValidAttachment)
Seems like you need:
let emailContainsValidAttachments =
List.exists ( fun r -> r = ValidAttachment) (List.map attachments processAttachment)
The argument order, for some reason, is different in exists vs. map.