yield and return
∼ Poor English ver. ∼
bleis-tift
July 27, 2014
Self introduction
https://twitter.com/bleis
https://github.com/bleis-tift
Yuru-Fuwa F#er in Nagoya
I like static typed functional languages
Agenda
Part1: Computation Expression
Part2:Dierence of yield and return
∼considerations∼
Part3:Dierence of yield and return
∼implementations∼
Part4:Conclusion
I will talk about how to implement computation
expression.
Part1: Computation Expression
Computation expression is...
expression that extends normal F# grammer and
provides some customize points
able to dene an user dene process
.
the grammer of F#
..
.
let someFunc a b =
let x = f a
let y = g b
x + y
.
computation expression
..
.
let someFunc a b = builder {
let! x = f a
let! y = g b
return x + y
}
Usages
Remove nests of match expressions for option
Hide parameters for state
Remove nests of function call for async proc
and so on...
But I will skip these topics today.
The way of implementation
The computation exprs are implemented by
some translation rules in F#
Needless any interfaces
Most important thing is which translated exprs are
compilable
.
.
grammer of computation expr
translate
normal grammer
Let's look at some translation rules together!
Notation
Sans-Serif Code of F#. ex) fun x - x
Serif meta Variables. ex) cexpr
Itaric The part related to the translation. ex)
T(e, C)
Translation rule (most outside)
.
. builder-expr { cexpr }
F#compiler translates following:
.
. let b = builder-expr in {| cexpr |}
b is a fresh variable.
builder-expr
Just a normal expression
Builder is evaluated only once
Dene methods called at runtime into builder
type
All methods are dened as instance method
{| ... |}
Translate expr to core language grammer
ex) {| cexpr |} ... translate cexpr
See below for further detais
cexpr
The most outer target of translation
Other computation expr is represented by ce
cexpr is translated by Delay-trans, Quote-trans,
Run-trans if necessary
Representation of the translation rules
The translation rules are described by T-notation
.
T-notation
..
.T(e, C)
e:The computation expr that will be translated
C:The context that was translated
Find the translation rule that match e, and translate
it
T-notation of {| cexpr |}
.
T-notation
..
. {| cexpr |} ≡ T(cexpr, λv.v)
λv.v is anonymous function
before dot: the parameter
after dot: the function body
v is the translated expression
Function application is done at compile-time
(not run-time)
Trans rule for return
.
Trans rule
..
. T(return e, C) = C(b.Return(e))
if cexpr is return 42 then:
.
Example
..
.
T(return 42, λv.v)
−→(λv.v)(b.Return(42))
−→b.Return(42)
Complete!
Trans rule for let
.
Trans rules
..
.
T(return e, C) = C(b.Return(e))
T(let p = e in ce, C) = T(ce, λv.C(let p = e in v))
.
Example
..
.
T(let x = 42 in return x, λv1.v1)
−→T(return x, λv2.(λv1.v1)(let x = 42 in v2))
−→(λv2.(λv1.v1)(let x = 42 in v2))(b.Return(x))
−→(λv1.v1)(let x = 42 in b.Return(x))
−→let x = 42 in b.Return(x)
Trans rule of if
.
Trans rules
..
.
{| cexpr |} ≡ T(cexpr, λv.v)
T(return e, C) = C(b.Return(e))
T(if e then ce1 else ce2, C) = C(if e then {| ce1 |} else {| ce2 |})
T(if e then ce, C) = C(if e then {| ce |} else b.Zero())
.
Example
..
.
T(if c then return 42, λv1.v1)
−→(λv1.v1)(if c then {| return 42 |} else b.Zero())
−→(λv1.v1)(if c then T(return 42, λv2.v2) else b.Zero())
−→(λv1.v1)(if c then (λv2.v2)(b.Return(42)) else b.Zero())
−→(λv1.v1)(if c then b.Return(42) else b.Zero())
−→if c then b.Return(42) else b.Zero()
Trans rule of ce1; ce2
.
Trans rules
..
.
{| cexpr |} ≡ T(cexpr, λv.v)
T(return e, C) = C(b.Return(e))
T(ce1; ce2, C) = C(b.Combine({| ce1 |},b.Delay(fun () - {| ce2 |})))
.
Example
..
.
T(return 10; return 20, λv1.v1)
−→(λv1.v1)(b.Combine({| return 10 |},b.Delay(fun () - {| return 20 |})))
−→(λv1.v1)
(b.Combine(T(return 10, λv2.v2),b.Delay(fun () - T(return 20, λv3.v3))))
−→(λv1.v1)
(b.Combine((λv2.v2)(b.Return(10)),b.Delay(fun () - (λv3.v3)(b.Return(20)))))
−→(λv1.v1)(b.Combine(b.Return(10),b.Delay(fun () - b.Return(20))))
−→b.Combine(b.Return(10),b.Delay(fun () - b.Return(20)))
Trans rule of while.
Trans rules
..
.
{| cexpr |} ≡ T(cexpr, λv.v)
T(return e, C) = C(b.Return(e))
T(if e then ce, C) = C(if e then {| ce |} else b.Zero())
T(ce1; ce2, C) = C(b.Combine({| ce1 |},b.Delay(fun () - {| ce2 |})))
T(while e do ce, C) = T(ce, λv.C(b.While(fun () - e,b.Delay(fun () - v))))
.
Example
..
.
T (while f() do if g() then return 42 done; return 0, λv1.v1)
−→(λv1.v1)(b.Combine({| while f() do if g() then return 42 |},b.Delay(fun () - {| return 0 |})))
−→(λv1.v1)(b.Combine(
T (if g() then return 42, λv2.b.While(fun () - f(),b.Delay(fun () - v2)))
,b.Delay(fun () - b.Return(0))))
−→(λv1.v1)(b.Combine(
(λv2.b.While(fun () - f(),b.Delay(fun () - v2)))(if g() then b.Return(42) else b.Zero())
,b.Delay(fun () - b.Return(0))))
−→(λv1.v1)(b.Combine(
b.While(fun () - f(),b.Delay(fun () - if g() then b.Return(42) else b.Zero()))
,b.Delay(fun () - b.Return(0))))
−→b.Combine(b.While(fun () - f(),b.Delay(fun () - if g() then b.Return(42) else b.Zero()))
,b.Delay(fun () - b.Return(0)))
Feature of computation expr
Computation expr is similar to
do notation (Haskell)
for expression (Scala)
query expression (C#)
Dierence is computation expr has more exibility
than core language.
Computation expr is more powerful and friendly!
Part2:Dierence of yield and return
∼considerations∼
Trans rules of yield and return
.
Trans rules
..
.
T(yield e, C) = C(b.Yield(e))
T(return e, C) = C(b.Return(e))
Dierent point is only method...
Today's main theme:
Why exist the same rules?
Use properly...?
use yield for yield-like and use return for
return-like...?
use yield for collection-like, otherwise uses
return...?
What's the xxx-like!
I want to decide clearly.
Thinking about dierence between yield
and return
Reer the dictionary:
yield produce/provide
return give back
return should not be continue the following
process.
Monad's return? I don't know:)
Dierence between yield and return
.
yield
..
.
list {
yield 1
printfn done
}
.return
..
.
list {
return 1
printfn done
}
Whether or not to print done
The case of C
return
IET
yield return
yield break
query expression
select
I want to realize something like yield return and
yield break.
seq expression
return is not supported
Dicult for yield break like C#
Let's reimplements seq expression by computation
expression!
Part3:Dierence of yield and return
∼implementations∼
Problem
The trans rule is same yield and return...
Plan 1
The focus on return breaks remained process
Need to return value when called return
Throw exception that wraps returning value in
Return method and catch the exception in Run
method
Impl by exception
.
Builder
..
.
type ReturnExn'T(xs: 'T seq) =
inherit System.Exception()
member this.Value = xs
type SeqBuilder'T() =
member this.Yield(x: 'T) = Seq.singleton x
member this.Return(x: 'T) =
raise (ReturnExn(Seq.singleton x))
member this.Combine(xs: 'T seq, cont: unit - 'T seq) =
Seq.append xs (cont ())
member this.Delay(f: unit - 'T seq) = f
member this.Run(f: unit - 'T seq) =
try f () with
| :? ReturnExn'T as e - e.Value
let seq2'T = SeqBuilder'T() // type function
Impl by exception
.
Usage
..
.
 seq2 { yield 1; yield 2 };;
val it : seqint = seq [1; 2]
 seq2 { return 1; return 2 };;
val it : seqint = seq [1]
Yes!
Impl by exception
Scala uses exception for the part of implements
return and break
Looks like easy
But!
Problem
.
Bad Example
..
.
 seq2 { yield 1; return 2; return 3 };;
val it : seqint = seq [2]
In C#:
.
C#
..
.
IEnumerableint F() {
yield return 1;
yield break 2;
yield break 3; }
It returns the sequencce contains 1 and 2.
Rene version
.
Catch ReturnExn in Combine
..
.
type SeqBuilder'T() =
member this.Yield(x: 'T) = Seq.singleton x
member this.Return(x: 'T) =
raise (ReturnExn(Seq.singleton x))
member this.Combine(xs: 'T seq, cont: unit - 'T seq) =
try
Seq.append xs (cont ())
with
| :? ReturnExn'T as e -
raise (ReturnExn(Seq.append xs e.Value))
member this.Delay(f: unit - 'T seq) = f
member this.Run(f: unit - 'T seq) =
try f () with
| :? ReturnExn'T as e - e.Value
let seq2'T = SeqBuilder'T()
Impl by exception
If provide try-with, need to catch ReturnExn
in try-with and reraise it
Eventually, can't implement clearly
Disinclined for use to exception for control ow
Could be realized at least
Plan 2
Continue or not continue
Insert the judgement of whether to call the rest
process
impl by state eld
.
Builder
..
.
type SeqBuilder() =
let mutable isExit = false
member this.Yield(x) = Seq.singleton x
member this.Return(x) =
isExit - true
Seq.singleton x
member this.Combine(xs, cont) =
if isExit then xs else Seq.append xs (cont ())
member this.Delay(f) = f
member this.Run(f) =
let res = f ()
isExit - false
res
let seq2 = SeqBuilder()
impl by state eld
.
Usage
..
.
 seq2 { yield 1; yield 2 };;
val it : seqint = seq [1; 2]
 seq2 { return 1; return 2 };;
val it : seqint = seq [1]
 seq2 { yield 1; return 2; return 3 };;
val it : seqint = seq [1; 2]
Yes!
impl by state eld
simple
looks like easy
But!
Problem
builder instance has state
use the same builder instance at the same time...
.
.
Thread A
seq2 {
yield 1
; // Combine
yield 2 // oops!
} // Run
val it : seqint = seq [1]
seq2.isExit
false
true
false
Thread B
seq2 {
return 10
} // Run
Rene version
.
Builder
..
.
type SeqBuilder() =
(* ... *)
let seq2 () = SeqBuilder()
.
Usage
..
.
 seq2 () { yield 1; yield 2 };;
val it : seqint = seq [1; 2]
 seq2 () { return 1; return 2 };;
val it : seqint = seq [1]
 seq2 () { yield 1; return 2; return 3 };;
val it : seqint = seq [1; 2]
Impl by state eld
Create the builder instance at every time
Can't forbid that the user share the instance
It's troublesome
Does not stand for practical use...
Plan 3
Problem: state sharing
Solution: use the argument
Carry the state by the argument, and unwrap the
state in Run method
The rest process is not called if the state is
Break in Combine method
Impl by state arg
.
Builder
..
.
type FlowControl = Break | Continue
type SeqBuilder() =
member this.Yield(x) = Seq.singleton x, Continue
member this.Return(x) = Seq.singleton x, Break
member this.Combine((xs, st), cont) =
match st with
| Break - xs, Break
| Continue -
let ys, st = cont ()
Seq.append xs ys, st
member this.Delay(f) = f
member this.Run(f) = f () | fst
let seq2 = SeqBuilder()
Impl by state arg
.
Usage
..
.
 seq2 { yield 1; yield 2 };;
val it : seqint = seq [1; 2]
 seq2 { return 1; return 2 };;
val it : seqint = seq [1]
 seq2 { yield 1; return 2; return 3 };;
val it : seqint = seq [1; 2]
Yes!
Impl by state arg
Symmetry of the return and yield became clear
The implementation is very complex
Looks like good.
Comparison
.
Impl by exception
..
.
member this.Yield(x: 'T) = Seq.singleton x
member this.Return(x: 'T) =
raise (ReturnExn(Seq.singleton x))
.
Impl by state eld
..
.
member this.Yield(x) = Seq.singleton x
member this.Return(x) =
isExit - true
Seq.singleton x
.
Impl by state arg
..
.
member this.Yield(x) = Seq.singleton x, Continue
member this.Return(x) = Seq.singleton x, Break
Plan 4
Impl of exception: use the exception to break
the rest process
It is same to discard continuation
yield: call continuation
return: discard continuation
Impl by continuation
.Builder
..
.
type SeqBuilder() =
member this.Yield(x) = fun k - k (Seq.singleton x)
member this.Return(x) = fun _ - Seq.singleton x
member this.Combine(f, cont) =
fun k - f (fun xs - cont () k | Seq.append xs)
member this.Delay(f) = f
member this.Run(f) = f () id
let seq2 = SeqBuilder()
.Usage
..
.
 seq2 { yield 1; yield 2 };;
val it : seqint = seq [1; 2]
 seq2 { return 1; return 2 };;
val it : seqint = seq [1]
 seq2 { yield 1; return 2; return 3 };;
val it : seqint = seq [1; 2]
Impl by continuation
Symmetry of return and yield is clear
Shortest but complex (and not dene the Bind
method)
The state arg version too
Speed Comparison
Write yield at 100,000 times and execute.
builder time
unsupported return 20.5ms
by exception 20.5ms
by state eld 20.7ms
by state arg 21.2ms
by continuation 22.6ms
seq expr 1.18ms
The dierence is less.
But builer is slower than seq expr in the rst place.
Part4 : Conclusion
Summary
The computation expression is powerful
yield and return have the same translation
rule but the meaning is dierent
The seq expression is not supported return →
reimplementation
Implementations:
by exception
by state eld (deprecated)
by state arg
by continuation
Impl status of some libraries
Design about return ex) seq/list/option
Target libraries:
FSharpx
ExtCore
FSharpPlus
Basis.Core
As of July 21, 2014
Impl status of some libraries
.
Benchmark code
..
.
let xs = [30; 10; 15; 21; -1; 50]
builder {
let i = ref 0
while !i  xs.Length do
if xs.[!i] = -1 then
return false
incr i
return true
}
Can compile it
It returns false-like value
Impl status of some libraries
.
Expanded benchmark code
..
.
let b = builder
b.Run(
b.Delay(fun () -
let i = ref 0
b.Combine(
b.While(
(fun () - !i  xs.Length),
b.Delay(fun () -
b.Combine(
(if xs.[!i] = -1 then b.Return(false)
else b.Zero()),
b.Delay(fun () - incr i; b.Zero())))),
b.Delay(fun () - b.Return(true)))))
FSharpx
can't compile...
FSharpx
The type of Combine is bad.
.
Signature of Combine
..
.'a option * ('a - 'b option) - 'b option
.
Expand of error point
..
.
// 'a option * ('a - 'b option) - 'b option
b.Combine(
// bool option
(if xs.[!i] = -1 then b.Return(false) else b.Zero()),
// unit - 'a option
b.Delay(fun () - incr i; b.Zero()))
.
Correct signature
..
.'a option * (unit - 'a option) - 'a option
ExtCore
can't compile...
ExtCore
The impl of Zero is bad.
.
Implementation of Zero
..
.
member inline __.Zero () : unit option =
Some () // TODO : Should this be None?
comment...
FSharpPlus
can't compile.
Not provide While
Better choice
Basis.Core
can compile.
return false-like value.
Impl status of some libraries
No Game!
Rethink about dierence yield and return
Very few libraries implement computation expr
correctly
There is a problem to be solved before yield and
return
Should we give a semantic dierence really?
Should give if you want to take advantage of
computation expr
Should not give if you provide only Bind and
Return (like FSharpPlus)
Rethink about computation expression
Should Yield and Return receive continuation?
Compile-time translation is ecient
Can implement yield and return by now rules
I want to take this exibility
Suggestion of a Policy
The considered separately depending on the library
design
Case 1: provide monad/monad plus
Case 2: provide more general computing
Case 3: use computaion expr other than monad
Provide monad/monad plus
Provide monad
Required: Bind/Return
Optional: ReturnFrom (for convinience)
Optional: Run
Provide another builder that unwrap the value
Provide monad plus
Required: Bind/Return/Zero/Combine
Zero is mzero, Combine is mplus
Required: Delay (depends on trans rule of
Combine)
member this.Delay(f) = f ()
Provide more general computing
Separate the modules by feature
Builder module for providing Bind/Return
Builder module for providing Bind/Return/Comine
Combine is not mplus. Combine + Delay is
mplus.
Inevitably, required Delay/Run
member this.Delay(f) = f
member this.Run(f) = f ()
Optional: Zero
Support if-expr without else-clause
Use computaion expr other than monad
I have no comments:)
If provide Combine, think about yield and return
Use CustomOperation if necessary
Tasks
Report the bug to FSharpx and ExtCore
Create a library that is divided the module by
feature
Verify builder
Edication
Thanks.

yield and return (poor English ver)

  • 1.
    yield and return ∼Poor English ver. ∼ bleis-tift July 27, 2014
  • 2.
  • 3.
    Agenda Part1: Computation Expression Part2:Dierenceof yield and return ∼considerations∼ Part3:Dierence of yield and return ∼implementations∼ Part4:Conclusion I will talk about how to implement computation expression.
  • 4.
  • 5.
    Computation expression is... expressionthat extends normal F# grammer and provides some customize points able to dene an user dene process . the grammer of F# .. . let someFunc a b = let x = f a let y = g b x + y . computation expression .. . let someFunc a b = builder { let! x = f a let! y = g b return x + y }
  • 6.
    Usages Remove nests ofmatch expressions for option Hide parameters for state Remove nests of function call for async proc and so on... But I will skip these topics today.
  • 7.
    The way ofimplementation The computation exprs are implemented by some translation rules in F# Needless any interfaces Most important thing is which translated exprs are compilable . . grammer of computation expr translate normal grammer Let's look at some translation rules together!
  • 8.
    Notation Sans-Serif Code ofF#. ex) fun x - x Serif meta Variables. ex) cexpr Itaric The part related to the translation. ex) T(e, C)
  • 9.
    Translation rule (mostoutside) . . builder-expr { cexpr } F#compiler translates following: . . let b = builder-expr in {| cexpr |} b is a fresh variable.
  • 10.
    builder-expr Just a normalexpression Builder is evaluated only once Dene methods called at runtime into builder type All methods are dened as instance method
  • 11.
    {| ... |} Translateexpr to core language grammer ex) {| cexpr |} ... translate cexpr See below for further detais
  • 12.
    cexpr The most outertarget of translation Other computation expr is represented by ce cexpr is translated by Delay-trans, Quote-trans, Run-trans if necessary
  • 13.
    Representation of thetranslation rules The translation rules are described by T-notation . T-notation .. .T(e, C) e:The computation expr that will be translated C:The context that was translated Find the translation rule that match e, and translate it
  • 14.
    T-notation of {|cexpr |} . T-notation .. . {| cexpr |} ≡ T(cexpr, λv.v) λv.v is anonymous function before dot: the parameter after dot: the function body v is the translated expression Function application is done at compile-time (not run-time)
  • 15.
    Trans rule forreturn . Trans rule .. . T(return e, C) = C(b.Return(e)) if cexpr is return 42 then: . Example .. . T(return 42, λv.v) −→(λv.v)(b.Return(42)) −→b.Return(42) Complete!
  • 16.
    Trans rule forlet . Trans rules .. . T(return e, C) = C(b.Return(e)) T(let p = e in ce, C) = T(ce, λv.C(let p = e in v)) . Example .. . T(let x = 42 in return x, λv1.v1) −→T(return x, λv2.(λv1.v1)(let x = 42 in v2)) −→(λv2.(λv1.v1)(let x = 42 in v2))(b.Return(x)) −→(λv1.v1)(let x = 42 in b.Return(x)) −→let x = 42 in b.Return(x)
  • 17.
    Trans rule ofif . Trans rules .. . {| cexpr |} ≡ T(cexpr, λv.v) T(return e, C) = C(b.Return(e)) T(if e then ce1 else ce2, C) = C(if e then {| ce1 |} else {| ce2 |}) T(if e then ce, C) = C(if e then {| ce |} else b.Zero()) . Example .. . T(if c then return 42, λv1.v1) −→(λv1.v1)(if c then {| return 42 |} else b.Zero()) −→(λv1.v1)(if c then T(return 42, λv2.v2) else b.Zero()) −→(λv1.v1)(if c then (λv2.v2)(b.Return(42)) else b.Zero()) −→(λv1.v1)(if c then b.Return(42) else b.Zero()) −→if c then b.Return(42) else b.Zero()
  • 18.
    Trans rule ofce1; ce2 . Trans rules .. . {| cexpr |} ≡ T(cexpr, λv.v) T(return e, C) = C(b.Return(e)) T(ce1; ce2, C) = C(b.Combine({| ce1 |},b.Delay(fun () - {| ce2 |}))) . Example .. . T(return 10; return 20, λv1.v1) −→(λv1.v1)(b.Combine({| return 10 |},b.Delay(fun () - {| return 20 |}))) −→(λv1.v1) (b.Combine(T(return 10, λv2.v2),b.Delay(fun () - T(return 20, λv3.v3)))) −→(λv1.v1) (b.Combine((λv2.v2)(b.Return(10)),b.Delay(fun () - (λv3.v3)(b.Return(20))))) −→(λv1.v1)(b.Combine(b.Return(10),b.Delay(fun () - b.Return(20)))) −→b.Combine(b.Return(10),b.Delay(fun () - b.Return(20)))
  • 19.
    Trans rule ofwhile. Trans rules .. . {| cexpr |} ≡ T(cexpr, λv.v) T(return e, C) = C(b.Return(e)) T(if e then ce, C) = C(if e then {| ce |} else b.Zero()) T(ce1; ce2, C) = C(b.Combine({| ce1 |},b.Delay(fun () - {| ce2 |}))) T(while e do ce, C) = T(ce, λv.C(b.While(fun () - e,b.Delay(fun () - v)))) . Example .. . T (while f() do if g() then return 42 done; return 0, λv1.v1) −→(λv1.v1)(b.Combine({| while f() do if g() then return 42 |},b.Delay(fun () - {| return 0 |}))) −→(λv1.v1)(b.Combine( T (if g() then return 42, λv2.b.While(fun () - f(),b.Delay(fun () - v2))) ,b.Delay(fun () - b.Return(0)))) −→(λv1.v1)(b.Combine( (λv2.b.While(fun () - f(),b.Delay(fun () - v2)))(if g() then b.Return(42) else b.Zero()) ,b.Delay(fun () - b.Return(0)))) −→(λv1.v1)(b.Combine( b.While(fun () - f(),b.Delay(fun () - if g() then b.Return(42) else b.Zero())) ,b.Delay(fun () - b.Return(0)))) −→b.Combine(b.While(fun () - f(),b.Delay(fun () - if g() then b.Return(42) else b.Zero())) ,b.Delay(fun () - b.Return(0)))
  • 20.
    Feature of computationexpr Computation expr is similar to do notation (Haskell) for expression (Scala) query expression (C#) Dierence is computation expr has more exibility than core language. Computation expr is more powerful and friendly!
  • 21.
    Part2:Dierence of yieldand return ∼considerations∼
  • 22.
    Trans rules ofyield and return . Trans rules .. . T(yield e, C) = C(b.Yield(e)) T(return e, C) = C(b.Return(e)) Dierent point is only method... Today's main theme: Why exist the same rules?
  • 23.
    Use properly...? use yieldfor yield-like and use return for return-like...? use yield for collection-like, otherwise uses return...? What's the xxx-like! I want to decide clearly.
  • 24.
    Thinking about dierencebetween yield and return Reer the dictionary: yield produce/provide return give back return should not be continue the following process. Monad's return? I don't know:)
  • 25.
    Dierence between yieldand return . yield .. . list { yield 1 printfn done } .return .. . list { return 1 printfn done } Whether or not to print done
  • 26.
    The case ofC return IET yield return yield break query expression select I want to realize something like yield return and yield break.
  • 27.
    seq expression return isnot supported Dicult for yield break like C# Let's reimplements seq expression by computation expression!
  • 28.
    Part3:Dierence of yieldand return ∼implementations∼
  • 29.
    Problem The trans ruleis same yield and return...
  • 30.
    Plan 1 The focuson return breaks remained process Need to return value when called return Throw exception that wraps returning value in Return method and catch the exception in Run method
  • 31.
    Impl by exception . Builder .. . typeReturnExn'T(xs: 'T seq) = inherit System.Exception() member this.Value = xs type SeqBuilder'T() = member this.Yield(x: 'T) = Seq.singleton x member this.Return(x: 'T) = raise (ReturnExn(Seq.singleton x)) member this.Combine(xs: 'T seq, cont: unit - 'T seq) = Seq.append xs (cont ()) member this.Delay(f: unit - 'T seq) = f member this.Run(f: unit - 'T seq) = try f () with | :? ReturnExn'T as e - e.Value let seq2'T = SeqBuilder'T() // type function
  • 32.
    Impl by exception . Usage .. . seq2 { yield 1; yield 2 };; val it : seqint = seq [1; 2] seq2 { return 1; return 2 };; val it : seqint = seq [1] Yes!
  • 33.
    Impl by exception Scalauses exception for the part of implements return and break Looks like easy But!
  • 34.
    Problem . Bad Example .. . seq2{ yield 1; return 2; return 3 };; val it : seqint = seq [2] In C#: . C# .. . IEnumerableint F() { yield return 1; yield break 2; yield break 3; } It returns the sequencce contains 1 and 2.
  • 35.
    Rene version . Catch ReturnExnin Combine .. . type SeqBuilder'T() = member this.Yield(x: 'T) = Seq.singleton x member this.Return(x: 'T) = raise (ReturnExn(Seq.singleton x)) member this.Combine(xs: 'T seq, cont: unit - 'T seq) = try Seq.append xs (cont ()) with | :? ReturnExn'T as e - raise (ReturnExn(Seq.append xs e.Value)) member this.Delay(f: unit - 'T seq) = f member this.Run(f: unit - 'T seq) = try f () with | :? ReturnExn'T as e - e.Value let seq2'T = SeqBuilder'T()
  • 36.
    Impl by exception Ifprovide try-with, need to catch ReturnExn in try-with and reraise it Eventually, can't implement clearly Disinclined for use to exception for control ow Could be realized at least
  • 37.
    Plan 2 Continue ornot continue Insert the judgement of whether to call the rest process
  • 38.
    impl by stateeld . Builder .. . type SeqBuilder() = let mutable isExit = false member this.Yield(x) = Seq.singleton x member this.Return(x) = isExit - true Seq.singleton x member this.Combine(xs, cont) = if isExit then xs else Seq.append xs (cont ()) member this.Delay(f) = f member this.Run(f) = let res = f () isExit - false res let seq2 = SeqBuilder()
  • 39.
    impl by stateeld . Usage .. . seq2 { yield 1; yield 2 };; val it : seqint = seq [1; 2] seq2 { return 1; return 2 };; val it : seqint = seq [1] seq2 { yield 1; return 2; return 3 };; val it : seqint = seq [1; 2] Yes!
  • 40.
    impl by stateeld simple looks like easy But!
  • 41.
    Problem builder instance hasstate use the same builder instance at the same time... . . Thread A seq2 { yield 1 ; // Combine yield 2 // oops! } // Run val it : seqint = seq [1] seq2.isExit false true false Thread B seq2 { return 10 } // Run
  • 42.
    Rene version . Builder .. . type SeqBuilder()= (* ... *) let seq2 () = SeqBuilder() . Usage .. . seq2 () { yield 1; yield 2 };; val it : seqint = seq [1; 2] seq2 () { return 1; return 2 };; val it : seqint = seq [1] seq2 () { yield 1; return 2; return 3 };; val it : seqint = seq [1; 2]
  • 43.
    Impl by stateeld Create the builder instance at every time Can't forbid that the user share the instance It's troublesome Does not stand for practical use...
  • 44.
    Plan 3 Problem: statesharing Solution: use the argument Carry the state by the argument, and unwrap the state in Run method The rest process is not called if the state is Break in Combine method
  • 45.
    Impl by statearg . Builder .. . type FlowControl = Break | Continue type SeqBuilder() = member this.Yield(x) = Seq.singleton x, Continue member this.Return(x) = Seq.singleton x, Break member this.Combine((xs, st), cont) = match st with | Break - xs, Break | Continue - let ys, st = cont () Seq.append xs ys, st member this.Delay(f) = f member this.Run(f) = f () | fst let seq2 = SeqBuilder()
  • 46.
    Impl by statearg . Usage .. . seq2 { yield 1; yield 2 };; val it : seqint = seq [1; 2] seq2 { return 1; return 2 };; val it : seqint = seq [1] seq2 { yield 1; return 2; return 3 };; val it : seqint = seq [1; 2] Yes!
  • 47.
    Impl by statearg Symmetry of the return and yield became clear The implementation is very complex Looks like good.
  • 48.
    Comparison . Impl by exception .. . memberthis.Yield(x: 'T) = Seq.singleton x member this.Return(x: 'T) = raise (ReturnExn(Seq.singleton x)) . Impl by state eld .. . member this.Yield(x) = Seq.singleton x member this.Return(x) = isExit - true Seq.singleton x . Impl by state arg .. . member this.Yield(x) = Seq.singleton x, Continue member this.Return(x) = Seq.singleton x, Break
  • 49.
    Plan 4 Impl ofexception: use the exception to break the rest process It is same to discard continuation yield: call continuation return: discard continuation
  • 50.
    Impl by continuation .Builder .. . typeSeqBuilder() = member this.Yield(x) = fun k - k (Seq.singleton x) member this.Return(x) = fun _ - Seq.singleton x member this.Combine(f, cont) = fun k - f (fun xs - cont () k | Seq.append xs) member this.Delay(f) = f member this.Run(f) = f () id let seq2 = SeqBuilder() .Usage .. . seq2 { yield 1; yield 2 };; val it : seqint = seq [1; 2] seq2 { return 1; return 2 };; val it : seqint = seq [1] seq2 { yield 1; return 2; return 3 };; val it : seqint = seq [1; 2]
  • 51.
    Impl by continuation Symmetryof return and yield is clear Shortest but complex (and not dene the Bind method) The state arg version too
  • 52.
    Speed Comparison Write yieldat 100,000 times and execute. builder time unsupported return 20.5ms by exception 20.5ms by state eld 20.7ms by state arg 21.2ms by continuation 22.6ms seq expr 1.18ms The dierence is less. But builer is slower than seq expr in the rst place.
  • 53.
  • 54.
    Summary The computation expressionis powerful yield and return have the same translation rule but the meaning is dierent The seq expression is not supported return → reimplementation Implementations: by exception by state eld (deprecated) by state arg by continuation
  • 55.
    Impl status ofsome libraries Design about return ex) seq/list/option Target libraries: FSharpx ExtCore FSharpPlus Basis.Core As of July 21, 2014
  • 56.
    Impl status ofsome libraries . Benchmark code .. . let xs = [30; 10; 15; 21; -1; 50] builder { let i = ref 0 while !i xs.Length do if xs.[!i] = -1 then return false incr i return true } Can compile it It returns false-like value
  • 57.
    Impl status ofsome libraries . Expanded benchmark code .. . let b = builder b.Run( b.Delay(fun () - let i = ref 0 b.Combine( b.While( (fun () - !i xs.Length), b.Delay(fun () - b.Combine( (if xs.[!i] = -1 then b.Return(false) else b.Zero()), b.Delay(fun () - incr i; b.Zero())))), b.Delay(fun () - b.Return(true)))))
  • 58.
  • 59.
    FSharpx The type ofCombine is bad. . Signature of Combine .. .'a option * ('a - 'b option) - 'b option . Expand of error point .. . // 'a option * ('a - 'b option) - 'b option b.Combine( // bool option (if xs.[!i] = -1 then b.Return(false) else b.Zero()), // unit - 'a option b.Delay(fun () - incr i; b.Zero())) . Correct signature .. .'a option * (unit - 'a option) - 'a option
  • 60.
  • 61.
    ExtCore The impl ofZero is bad. . Implementation of Zero .. . member inline __.Zero () : unit option = Some () // TODO : Should this be None? comment...
  • 62.
  • 63.
  • 64.
    Impl status ofsome libraries No Game!
  • 65.
    Rethink about dierenceyield and return Very few libraries implement computation expr correctly There is a problem to be solved before yield and return Should we give a semantic dierence really? Should give if you want to take advantage of computation expr Should not give if you provide only Bind and Return (like FSharpPlus)
  • 66.
    Rethink about computationexpression Should Yield and Return receive continuation? Compile-time translation is ecient Can implement yield and return by now rules I want to take this exibility
  • 67.
    Suggestion of aPolicy The considered separately depending on the library design Case 1: provide monad/monad plus Case 2: provide more general computing Case 3: use computaion expr other than monad
  • 68.
    Provide monad/monad plus Providemonad Required: Bind/Return Optional: ReturnFrom (for convinience) Optional: Run Provide another builder that unwrap the value Provide monad plus Required: Bind/Return/Zero/Combine Zero is mzero, Combine is mplus Required: Delay (depends on trans rule of Combine) member this.Delay(f) = f ()
  • 69.
    Provide more generalcomputing Separate the modules by feature Builder module for providing Bind/Return Builder module for providing Bind/Return/Comine Combine is not mplus. Combine + Delay is mplus. Inevitably, required Delay/Run member this.Delay(f) = f member this.Run(f) = f () Optional: Zero Support if-expr without else-clause
  • 70.
    Use computaion exprother than monad I have no comments:) If provide Combine, think about yield and return Use CustomOperation if necessary
  • 71.
    Tasks Report the bugto FSharpx and ExtCore Create a library that is divided the module by feature Verify builder Edication
  • 72.