这些天来学习F#时,我注意到在某些图书馆中,例如this one或that 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
。
如果有人可以暗示这些功能的实用性,我将不胜感激。
答案 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>
的函数。
例如,给定标准的Result类型:
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.map2
或List.map3
,但可以推广到任意数量的参数列表。
旁注:如果您查找“应用函子”,您将发现大多数结果将在Haskell中,在此地图操作符通常用F#编写为<!>
,写为<$>
代替。