这些F#函数应该做什么?

时间:2019-05-31 08:25:34

标签: f#

这些天来学习F#时,我注意到在某些图书馆中,例如this onethat one 还有一些类似的功能似乎在F#中很常见,但无法真正解读它们,它们在做什么,它们是做什么的?

let ap x f =
    match f, x with
    | Ok f        , Ok x    -> Ok (f x)
    | Error e     , _       -> Error e
    | _           , Error e -> Error e
let inline (<*>) f x = ap x f
let inline (<!>) f x = Result.map f x
let inline lift2 f a b = f <!> a <*> b

即使将评论汇总在一起,对我的理解也无济于事:

/// Sequential application
/// If the wrapped function is a success and the given result is a success the function is applied on the value. 
/// Otherwise the exisiting error messages are propagated.
let ap x f =
match f,x with
    | Ok f        , Ok x    -> Ok (f x)
    | Error e     , _       -> Error e
    | _           , Error e -> Error e

/// Sequential application
/// If the wrapped function is a success and the given result is a success the function is applied on the value. 
/// Otherwise the exisiting error messages are propagated.
let inline (<*>) f x = ap x f

/// Infix map, lifts a function into a Result and applies it on the given result.
let inline (<!>) f x = Result.map f x

/// Promote a function to a monad/applicative, scanning the monadic/applicative arguments from left to right.
let inline lift2 f a b = f <!> a <*> b

我什至没有看到如何使用它们的示例,也不确定为什么使用inline

如果有人可以暗示这些功能的实用性,我将不胜感激。

2 个答案:

答案 0 :(得分:4)

Scott Wlaschin的 F#为娱乐和利润https://fsharpforfunandprofit.com)有一系列 Map and Bind and Apply,噢,我的!https://fsharpforfunandprofit.com/posts/elevated-world-7)这应该能够对此有所启发。关于您的特定问题:

  • <!>map运算符,它将函数f和参数x应用于要映射到的数据结构的元素,即换句话说,是提升函数进入数据结构领域,在这种情况下为Result类型。
  • <*>ap(应用)运算符,用于将包装在升高值内的函数解压缩为提升函数。
  • lift2基本上是用于两参数函数的map运算符。

请看一下博客,它确实有帮助!

答案 1 :(得分:4)

这些被称为“应用函子”(有时也称为“应用函子”)。它们的目的是使用一个函数合并来自多个Something<'T>的数据。基本上,将类型为'Arg1 -> 'Arg2 -> ... -> 'Result的函数“提升”为类型为Something<'Arg1> -> Something<'Arg2> -> ... -> Something<'Result>的函数。

例如,给定标准的Res​​ult类型:

type Result<'T, 'Err> = Ok of 'T | Error of 'Err

您可能需要将多个结果值组合在一起。例如,假设您有一个带有输入firstName,lastName和age的表单。您也有一个结果类型Person

type Person = { firstName: string; lastName: string; age: int }

// string -> string -> int -> Person
let makePerson firstName lastName age =
    { firstName = firstName; lastName = lastName; age = age }

来自您实际形式的值可能具有类型Result<string, InputError>Result<int, InputError>,例如,可以为Error。用户尚未输入值。

type InputError =
    | FieldMissing of fieldName: string
    // Other error cases...

您想将它们组合为Result<Person, InputError>,如果所有输入均为Ok,则为Ok;如果任何输入为Error,则为Error。使用该应用程序,您可以这样做:

// Result<string, InputError> -> Result<string, InputError> -> Result<int, InputError> -> Result<Person, InputError>
let makePersonResult firstName lastName age =
    makePerson <!> firstName <*> lastName <*> age

// Example uses:

makePersonResult (Ok "John") (Ok "Doe") (Ok 42)
// --> Ok { firstName = "John"; lastName = "Doe"; age = 42 }
makePersonResult (Error (FieldMissing "firstName")) (Ok "Doe") (Ok 42)
// --> Error (FieldMissing "firstName")

除了Result之外,类似的概念还可以应用于许多其他类型,这就是为什么要为其指定名称的原因。例如,Async<'T>上的应用程序可以并行运行所有参数Asyncs,完成后将其结果合并到Async<'Result>中。另一个示例,在'T list上的应用程序等同于标准库的List.map2List.map3,但可以推广到任意数量的参数列表。

旁注:如果您查找“应用函子”,您将发现大多数结果将在Haskell中,在此地图操作符通常用F#编写为<!>,写为<$>代替。