Складной список опций

Учитывая список [Some 1; Some 2; Some 3], я хотел бы получить результат Some 6. Учитывая список [Some 1; None] должен дать None.

Но я нахожу это немного сложнее, чем я себе представлял, чтобы добиться этого чистым способом.

Лучшее, что я мог придумать, это

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 )

person Overly Excessive    schedule 05.03.2016    source источник
comment
Это обход, если бы у вас было sequenceA это было бы sequenceA [Some 1; Some 2; Some 3] |> Option.map List.sum ищите код для sequenceA, он есть в FsControl.   -  person Gus    schedule 05.03.2016
comment
@Gustavo;) вы должны сделать это ответом - хотя я думаю, что FsControl просто немного тяжеловат для общего F#er ... pure Haskell завидует, если хотите ^^   -  person Random Dev    schedule 05.03.2016
comment
Да, @Carsten, я согласен, что это слишком тяжело только для этого кода, поэтому я сделал это в качестве комментария. Это однострочное решение, его можно обобщить и сократить до sequenceA [Some 1; Some 2; Some 3] |>> sum.   -  person Gus    schedule 05.03.2016


Ответы (3)


Этого можно добиться, определив функцию map2 для значений параметров:

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

Это позволит вам написать нужную функцию:

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

Пример:

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

На данный момент функция optionMap2 недоступна в основной библиотеке F#, но вероятно, станет частью модуля Option в будущем.

person Mark Seemann    schedule 05.03.2016
comment
хотя это более или менее ответ Functional_S - он просто назвал его lift - person Random Dev; 05.03.2016
comment
да - я сначала удалил другой ответ, затем отредактировал его и восстановил - person Random Dev; 05.03.2016
comment
это нормально, чтобы дать другой ответ, и я уверен, что вы его не видели - просто немного грустно, что теперь вы получаете дополнительную репутацию (я думаю, из-за вашей более высокой общей статистики) вместо виртуального идентичного ответа, который пришел за несколько минут до ... но это не про тебя, так что не волнуйся - person Random Dev; 05.03.2016
comment
Я чувствую, что optionMap2 лучше следует соглашениям об именах F #, с более прагматичными именами, а не с двусмысленными именами. - person Overly Excessive; 05.03.2016

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

со складкой

[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
person Functional_S    schedule 05.03.2016
comment
Это работает только при условии, что список содержит записи, которые Some<int>однако. Нет, если у вас есть записи со значением None, в этом случае сокращение должно быть None. - person Overly Excessive; 05.03.2016
comment
хорошо, это дополнительная спецификация ;-) Вы не сказали, что именно хотите, вы привели только один примерный случай. - person Functional_S; 05.03.2016
comment
Это убивает мой ответ, поэтому попробуйте удалить его. - person Functional_S; 05.03.2016
comment
Использование List.reduce означает, что он рухнет, если будет указан пустой список ([]). Вместо этого рассмотрите возможность использования List.fold. - person Mark Seemann; 05.03.2016

Вот наивная реализация того, о чем говорил sequence Густаво:

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

(обратите внимание, что это не хвостовая рекурсия и вообще не оптимизировано, поэтому вам следует преобразовать его, если он вам понадобится для больших списков)

Что будет работать так же, как Густаво сказал вам:

> 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    
person Random Dev    schedule 05.03.2016