Skip to content

Commit f0cd254

Browse files
authored
Applicative Computation Expressions (#498)
1 parent 19ecf22 commit f0cd254

File tree

4 files changed

+106
-22
lines changed

4 files changed

+106
-22
lines changed

docsrc/content/abstraction-applicative.fsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ From F#+
9292
- [``ZipList<'T>``](type-ziplist.html)
9393
- [``ParallelArray<'T>``](type-parallelarray.html)
9494
- [``Const<'C,'T>``](type-const.html)
95-
- [``Compose<'ApplicativeF<'ApplicativeG<'T>>>``](type-compose.html)
95+
- [``Compose<'Applicative1<'Applicative2<'T>>>``](type-compose.html)
9696
- [``DList<'T>``](type-dlist.html)
9797
- [``Vector<'T,'Dimension>``](type-vector.html)
9898
- [``Matrix<'T,'Rows,'Columns>``](type-matrix.html)
@@ -141,7 +141,7 @@ let resLazy22 : Lazy<_> = result 22
141141
let (quot5 : Microsoft.FSharp.Quotations.Expr<int>) = result 5
142142

143143
// Example
144-
type Person = { name: string; age: int } with static member create n a = {name = n; age = a}
144+
type Person = { Name: string; Age: int } with static member create n a = { Name = n; Age = a }
145145

146146
let person1 = Person.create <!> tryHead ["gus"] <*> tryParse "42"
147147
let person2 = Person.create <!> tryHead ["gus"] <*> tryParse "fourty two"
@@ -190,18 +190,43 @@ let optFalse = tryParse "30" .< 29
190190
let m1m2m3 = -.[1;2;3]
191191

192192

193+
// Using applicative computation expression
193194

195+
let getName s = tryHead s
196+
let getAge s = tryParse s
194197

195-
// Composing applicatives
198+
let person4 = applicative {
199+
let! name = getName ["gus"]
200+
and! age = getAge "42"
201+
return { Name = name; Age = age } }
202+
203+
204+
(**
205+
206+
Composing applicatives
207+
----------------------
208+
209+
Unlike monads, applicatives are always composable.
210+
211+
The date type [``Compose<'Applicative1<'Applicative2<'T>>>``](type-compose.html) can be used to compose any 2 applicatives:
212+
*)
196213

197214
let res4 = (+) <!> Compose [Some 3] <*> Compose [Some 1]
198215

199-
let getName s = async { return tryHead s }
200-
let getAge s = async { return tryParse s }
216+
let getNameAsync s = async { return tryHead s }
217+
let getAgeAsync s = async { return tryParse s }
201218

202-
let person4 = Person.create <!> Compose (getName ["gus"]) <*> Compose (getAge "42")
219+
let person5 = Person.create <!> Compose (getNameAsync ["gus"]) <*> Compose (getAgeAsync "42")
203220

221+
(**
222+
223+
The computation expressions applicative2 and applicative3 can also be used to compose applicatives:
224+
*)
204225

226+
let person6 = applicative2 {
227+
let! name = printfn "aa"; getNameAsync ["gus"]
228+
and! age = getAgeAsync "42"
229+
return { Name = name; Age = age } }
205230

206231

207232

docsrc/content/computation-expressions.fsx

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ Computations Expressions
1212
1313
This library allows to use some common computation expressions without writing any boiler plate code.
1414
15-
There is a single computation expression: ``monad`` but it comes in 4 flavours:
15+
For applicatives there is single computation expression: ``applicative { .. }``. Additionally ``applicative2 { .. }`` and ``applicative3 { .. }`` exists for composed (aka layered) applicatives.
16+
17+
For monadic code there is a single computation expression: ``monad { .. }`` but it comes in 4 flavours:
1618
1719
- Delayed or strict
1820
@@ -55,21 +57,6 @@ let _ : OptionT<seq<unit option>> = monad { printfn "I'm strict" }
5557

5658
(**
5759
58-
Applicatives
59-
============
60-
61-
There are some F# issues preventing applicative required `BindReturn` to be included in `monad`, so for the moment the following snipped can be used to quickly create a generic applicative CE:
62-
63-
*)
64-
65-
type ApplicativeBuilder<'t> () =
66-
inherit MonadFxStrictBuilder<'t> ()
67-
member inline _.BindReturn (x, f) = map f x
68-
69-
let applicative<'t> = ApplicativeBuilder<'t> ()
70-
71-
(**
72-
7360
7461
Examples
7562
========

src/FSharpPlus/Builders.fs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace FSharpPlus
1818
module GenericBuilders =
1919

2020
open FSharpPlus.Operators
21+
open FSharpPlus.Data
2122

2223
// Idiom brackets
2324
type Ii = Ii
@@ -178,10 +179,51 @@ module GenericBuilders =
178179
else this.strict.While (enum.MoveNext, fun () -> rest enum.Current))
179180

180181

182+
/// Generic Applicative CE builder.
183+
type ApplicativeBuilder<'``applicative<'t>``> () =
184+
member _.ReturnFrom (expr) = expr : '``applicative<'t>``
185+
member inline _.Return (x: 'T) = result x : '``Applicative<'T>``
186+
member inline _.Yield (x: 'T) = result x : '``Applicative<'T>``
187+
member inline _.BindReturn(x, f) = map f x : '``Applicative<'U>``
188+
member inline _.MergeSources (t1: '``Applicative<'T>``, t2: '``Applicative<'U>``) : '``Applicative<'T * 'U>`` = Lift2.Invoke tuple2 t1 t2
189+
member inline _.MergeSources3 (t1: '``Applicative<'T>``, t2: '``Applicative<'U>``, t3: '``Applicative<'V>``) : '``Applicative<'T * 'U * 'V>`` = Lift3.Invoke tuple3 t1 t2 t3
190+
member _.Run f = f : '``applicative<'t>``
191+
192+
/// Generic 2 layers Applicative CE builder.
193+
type ApplicativeBuilder2<'``applicative1<applicative2<'t>>``> () =
194+
member _.ReturnFrom expr : '``applicative1<applicative2<'t>>`` = expr
195+
member inline _.Return (x: 'T) : '``Applicative1<Applicative2<'T>>`` = (result >> result) x
196+
member inline _.Yield (x: 'T) : '``Applicative1<Applicative2<'T>>`` = (result >> result) x
197+
member inline _.BindReturn (x: '``Applicative1<Applicative2<'T>>``, f: _ -> _) : '``Applicative1<Applicative2<'U>>`` = (map >> map) f x
198+
member inline _.MergeSources (t1, t2) : '``Applicative1<Applicative2<'T>>`` = (lift2 >> lift2) tuple2 t1 t2
199+
member inline _.MergeSources3 (t1, t2, t3) : '``Applicative1<Applicative2<'T>>`` = (lift3 >> lift3) tuple3 t1 t2 t3
200+
member _.Run x : '``applicative1<applicative2<'t>>`` = x
201+
202+
/// Generic 3 layers Applicative CE builder.
203+
type ApplicativeBuilder3<'``applicative1<applicative2<applicative3<'t>>>``> () =
204+
member _.ReturnFrom expr : '``applicative1<applicative2<applicative3<'t>>>`` = expr
205+
member inline _.Return (x: 'T) : '``Applicative1<Applicative2<Applicative3<'T>>>`` = (result >> result >> result) x
206+
member inline _.Yield (x: 'T) : '``Applicative1<Applicative2<Applicative3<'T>>>`` = (result >> result >> result) x
207+
member inline _.BindReturn (x: '``Applicative1<Applicative2<Applicative3<'T>>>``, f: _ -> _) : '``Applicative1<Applicative2<'U>>`` = (map >> map >> map) f x
208+
member inline _.MergeSources (t1, t2) : '``Applicative1<Applicative2<Applicative3<'T>>>`` = (lift2 >> lift2 >> lift2) tuple2 t1 t2
209+
member inline _.MergeSources3 (t1, t2, t3) : '``Applicative1<Applicative2<Applicative3<'T>>>`` = (lift3 >> lift3 >> lift3) tuple3 t1 t2 t3
210+
member _.Run x : '``applicative1<applicative2<applicative3<'t>>>`` = x
211+
212+
213+
181214
/// Creates a (lazy) monadic computation expression with side-effects (see http://fsprojects.github.io/FSharpPlus/computation-expressions.html for more information)
182215
let monad<'``monad<'t>``> = new MonadFxBuilder<'``monad<'t>``> ()
183216

184217
/// Creates a strict monadic computation expression with side-effects (see http://fsprojects.github.io/FSharpPlus/computation-expressions.html for more information)
185218
let monad'<'``monad<'t>``> = new MonadFxStrictBuilder<'``monad<'t>``> ()
186219

220+
/// Creates an applicative computation expression.
221+
let applicative<'``Applicative<'T>``> = ApplicativeBuilder<'``Applicative<'T>``> ()
222+
223+
/// Creates an applicative computation expression which compose effects of two Applicatives.
224+
let applicative2<'``Applicative1<Applicative2<'T>>``> = ApplicativeBuilder2<'``Applicative1<Applicative2<'T>>``> ()
225+
226+
/// Creates an applicative computation expression which compose effects of three Applicatives.
227+
let applicative3<'``Applicative1<Applicative2<Applicative3<'T>>>``> = ApplicativeBuilder3<'``Applicative1<Applicative2<Applicative3<'T>>>``> ()
228+
187229
#endif

tests/FSharpPlus.Tests/ComputationExpressions.fs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,36 @@ module ComputationExpressions =
1313

1414
let task<'t> = monad'<Task<'t>>
1515

16+
[<Test>]
17+
let twoLayersApplicatives () =
18+
let id : Task<Validation<_, string>> = Failure (Map.ofList ["Id", ["Negative number"]]) |> Task.FromResult
19+
let firstName : Validation<_, string> = Failure (Map.ofList ["Name", ["Invalid chars"]])
20+
let lastName : Validation<_, string> = Failure (Map.ofList ["Name", ["Too long"]])
21+
let date : Task<Validation<_, DateTime>> = Failure (Map.ofList ["DoB" , ["Invalid date"]]) |> result
22+
23+
let _person = applicative2 {
24+
let! i = id
25+
and! f = result firstName
26+
and! l = result lastName
27+
and! d = date
28+
return {| Id = i; Name = f + l; DateOfBirth = d |} }
29+
()
30+
31+
[<Test>]
32+
let threeLayersApplicatives () =
33+
let id : Lazy<Task<Validation<Map<string, string list>, int>>> = lazy (Failure (Map.ofList ["Id", ["Negative number"]]) |> result)
34+
let firstName : Task<Validation<Map<string, string list>, string>> = Failure (Map.ofList ["Name", ["Invalid chars"]]) |> Task.FromResult
35+
let lastName = "Smith"
36+
let date : Lazy<Task<Validation<Map<string, string list>, DateTime>>> = lazy (Failure (Map.ofList ["DoB" , ["Invalid date"]]) |> result)
37+
38+
let _person = applicative3 {
39+
let! i = id
40+
and! d = date
41+
and! f = result firstName
42+
let l = lastName
43+
return {| Id = i; Name = f + l ; DateOfBirth = d |} }
44+
()
45+
1646
[<Test>]
1747
let specializedCEs () =
1848

0 commit comments

Comments
 (0)