13

Given a list [Some 1; Some 2; Some 3] I would like an output Some 6 . Given a list [Some 1; None] should yield None.

But I'm finding it a bit more difficult than I had imagined to achieve this in a clean way.

The best I could come up with was this

let someNums = [Some 1; Some 2; Some 3]
someNums 
|> List.reduce (fun st v ->
  Option.bind (fun x ->
    Option.map (fun y -> x + y) st) v )
Overly Excessive
  • 2,095
  • 16
  • 31
  • 4
    That's a traverse, if you had `sequenceA` it would be `sequenceA [Some 1; Some 2; Some 3] |> Option.map List.sum` look for the code for sequenceA, FsControl has it. – Gus Mar 05 '16 at 10:36
  • @Gustavo ;) you should make this an answer - although I think `FsControl` is just a bit heavy for the *common* F#er ... `pure` Haskell envy if you will ^^ – Random Dev Mar 05 '16 at 10:39
  • Yes @Carsten, I agree is too heavy just for this code, that's why I made it as a comment. It's a one liner solution, it could be generalised and further reduced to `sequenceA [Some 1; Some 2; Some 3] |>> sum`. – Gus Mar 05 '16 at 10:53

3 Answers3

10
let lift op a b =
    match a, b with
    | Some av, Some bv  -> Some(op av bv)
    | _, _ -> None

let plus = lift (+)

[Some 1; Some 2; Some 3]
|> List.reduce plus
// val it : int option = Some 6


[Some 1; None]
|> List.reduce plus
// val it : int option = None

with fold

[Some 1; None]
|> List.fold plus (Some 0)
// val it : int option = None

[Some 1; Some 2; Some 3]
|> List.fold plus (Some 0)
// val it : int option = Some 6

[Some 1; None; Some 2] 
|> List.fold plus (Some 0)
// val it : int option = None
Functional_S
  • 1,159
  • 6
  • 9
7

You can accomplish this by defining a map2 function for option values:

let optionMap2 f x y =
    match x, y with
    | (Some x', Some y') -> Some (f x' y')
    | _ -> None

This would enable you to write the function you want:

let sumSome = List.fold (optionMap2 (+)) (Some 0)

Example:

> [Some 1; Some 2; Some 3] |> sumSome;;
val it : int option = Some 6
> [Some 1; None; Some 3] |> sumSome;;
val it : int option = None

At the moment, the optionMap2 function isn't available in the F# core library, but probably will be part of the Option module in the future.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 1
    this is more or less Functional_S 's answer though - he just called it `lift` – Random Dev Mar 05 '16 at 10:52
  • yeah - I first deleted another answer then editited it and undeleted it – Random Dev Mar 05 '16 at 10:53
  • 2
    it's fine to give another answer and I'm sure you did not see it - it's just a bit sad that that you now get the additional rep (I guess because of your higher overall stats) instead of the virtual identical answer that came minutes before ... but that's not about you so don't worry – Random Dev Mar 05 '16 at 10:55
  • 1
    I feel like ``optionMap2``follows the F# naming conventions better though, with more pragmatic naming over ambigious naming. – Overly Excessive Mar 05 '16 at 10:56
4

Here is an naive implementation of the sequence Gustavo talked about:

let rec sequence = 
   function 
   | [] -> Some [] 
   | (Some o :: os) -> 
      sequence os 
      |> Option.map (fun os' -> o::os') 
   | _ -> None

(please note that this is not tail-recursive and not optimized at all so you should transform it if you gonna need it for large lists)

Which would work just as Gustavo told you:

> sequence [Some 1; Some 2; Some 2] |> Option.map List.sum;;
val it : int option = Some 5

> sequence [Some 1; None; Some 2] |> Option.map List.sum;;
val it : int option = None    
Random Dev
  • 51,810
  • 9
  • 92
  • 119