6

My code contains quite a few double option types; I've been using the Option.map function quite successfully so far to eliminate the need to have to match on Some and None all over the place and treat them as lifted types but am not sure what to do in the following scenario:

let multiplyTwoOptions option1 option2 : double option = if not (option1.IsSome && option2.IsSome) then None else Some (option1.Value * option2.Value) 

I've read that you shouldn't use IsSome in this way, but the alternative (as far as I can see, to pattern match on both sequentially, seems quite long winded). I'm still quite new to F# so wondered if there is a more idiomatic way? I feel like maybe I need something like an Option.fold2 to act on two options at once but there isn't one.

4 Answers 4

14

Patterns can be nested, that's their great power. In this particular case, you can pattern match on the tuple:

match option1, option2 with | Some x, Some y -> Some (x * y) | _ -> None 
Sign up to request clarification or add additional context in comments.

Comments

8

The correct answer is in fact the following:

https://fsharpforfunandprofit.com/posts/elevated-world/#apply

If one needs some code then it boils down to about the following example:

module Option = // The apply function for Options let apply fOpt xOpt = match fOpt,xOpt with | Some f, Some x -> Some (f x) | _ -> None let (<!>) = Option.map let (<*>) = Option.apply let a = Some(4) let b = Some(5) let multiplication = (*) //Some multiplication function applied on a and resulting function applied on b let res1 = Some(multiplication) <*> a <*> b let res2 = Some(*) <*> a <*> b //Map a onto multiplication function and resulting function applied on b let res3 = multiplication <!> a <*> b let res4 = (*) <!> a <*> b val res1 : int option = Some 20 val res2 : int option = Some 20 val res3 : int option = Some 20 val res4 : int option = Some 20 //The following is without any options to try to clarify the above let op = (*) //multiplication //let partialRes = (*) 4 let partialRes = op 4 //make function for multiplying param with 4 let fullres = partialRes 5 //use function for multiplying with 4 val op : (int -> int -> int) val partialRes : (int -> int) val fullres : int = 20 

The reason for saying this is that with the above one can work in and out of what Wlaschin calls "elevated world", or Option in this case, and mix stuff from both. Kind of. Read the full site or book Wlaschin has written for good measure.

There is no need to make any function taking an Option as a param and the wrapping and unwrapping can be taken care of once and for all.

As the above code shows (shamelessly stolen from link, and somewhat rewritten), the function Richard needs is:

Option.apply 

Yes the symbols will possibly confuse especially since we are talking multiplication or * here, but these symbols for map <!> and apply <*> are somewhat 'standard'.

I think the comments in the code are more or less correct with regards to how to read the code.

And yes, I maybe need to work on my teaching styles ;-)

4 Comments

Thank you for this very thorough post, I'll need to grok this when I have a minute =)
I would not use the word 'grok' and 'minute' in same sentence ;-) Especially not to this theme. I still dont grok it, and need to think very carefully about every detail to make sure I get it right. And then I always wonders: Did I get it right... This post was done to help me grok too...
Not sure if I would call it the correct answer. Applicative functors are hardly part of F# curriculum.
If one are using Options then it should be. But yes. Its hard both to grok (for me at least) and reuse for real (language is not really suited even though functional first)
3

Your fold2 idea is not off the mark. Even if it's not part of standard library, you can easily implement such functions yourself.

Here's a bind2:

module Option = let bind2 f a b = match a, b with | Some a, Some b -> f a b | _, _ -> None 

I've seen bind3 used as well, but that's probably stretching it. I doubt there's much practical use for more than 3 arguments. You can implement different arities of map, iter or fold following a similar scheme.

This is not as robust or formally elegant as using applicative functors, but it solves the problem without introducing much conceptual overhead.

Comments

3

I'd go with the match, but as an alternative you can also use the maybe expression from https://www.nuget.org/packages/FSharpx.Extras/

let multiplyTwoOptions option1 option2 : double option = maybe { let! x = option1 let! y = option2 return x * y } 

While for a known set of inputs the match is more straightforward, you'll note the maybe expression is nicer for longer ad-hoc expressions:

maybe { let! a = getAOption() let! b = getBOption a let! c = getCOption a b return! getFinalOption a b c } 

vs

match getAOption() with | None -> None | Some a -> match getBOption a with | None -> None | Some b -> match getCOption a b with | None -> None | Some c -> getFinalOption a b c 

This also has an advantage over the applicative functor style in that e.g. the function generating b can depend on a, and c can depend on a and b, and so on, which is not possible in applicative style. The computation expression syntax also makes it easier to understand than applicative functors which have nothing but the raw operators.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.