Reputation: 9057
Learning F# these days, I've noticed that in some libraries like this one or that one there are some similar functions which seem to be common in F# but can't really decipher them, what are they doing, what are they for?
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
Even aggregating comments with them does not really help in my understanding:
/// 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
I don't even see an example of how they could be used, not sure also why inline
has been used.
If there is somebody who could hint about how useful those functions are, I would greatly appreciate.
Upvotes: 1
Views: 195
Reputation: 11372
These are called "applicative functors" (sometimes just "applicatives"). Their purpose is to combine data from multiple Something<'T>
using a function. Basically, "lifting" a function of type 'Arg1 -> 'Arg2 -> ... -> 'Result
into a function of type Something<'Arg1> -> Something<'Arg2> -> ... -> Something<'Result>
.
For example, given the standard Result type:
type Result<'T, 'Err> = Ok of 'T | Error of 'Err
you may have several Result values that you want to combine together. For example, say you have a form with inputs firstName, lastName and age. You also have a result type Person
:
type Person = { firstName: string; lastName: string; age: int }
// string -> string -> int -> Person
let makePerson firstName lastName age =
{ firstName = firstName; lastName = lastName; age = age }
The values coming from your actual form may have type Result<string, InputError>
or Result<int, InputError>
, which can be Error
if eg. the user hasn't entered a value.
type InputError =
| FieldMissing of fieldName: string
// Other error cases...
You want to combine them into a Result<Person, InputError>
, which is Ok
if all inputs are Ok
, or Error
if any input is Error
. Using the applicative, you can do it like this:
// 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")
A similar concept can be applied to many other types than Result, which is why it was given a name. For example, an applicative on Async<'T>
could run all the argument Asyncs in parallel, and when they're finished, combine their results into an Async<'Result>
. Another example, an applicative on 'T list
would be equivalent to the standard library's List.map2
or List.map3
but generalizable to any number of argument lists.
Side note: if you look up "applicative functor", most of the results you'll find will be in Haskell, where the map operator, usually written <!>
in F#, is written <$>
instead.
Upvotes: 5
Reputation: 1991
Scott Wlaschin's F# for fun and profit (https://fsharpforfunandprofit.com) has a series Map and Bind and Apply, Oh my! (https://fsharpforfunandprofit.com/posts/elevated-world-7) which should be able to shed more light on this. Regarding your particular question:
<!>
is the map
operator which applies a function f
and a parameter x
to elements of the data structure you are mapping over, or in other words, lifts the function into the realm of the data structure, in this case the Result
type.<*>
is the ap
(apply) operator which unpacks a function wrapped inside a elevated value into a lifted function.lift2
is basically the map
operator for a two-parameter function.Please have a look at the blog, it really helps!
Upvotes: 5