Top Banner
72

yield and return (poor English ver)

Jun 30, 2015

Download

Technology

bleis tift
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: yield and return (poor English ver)

yield and return

~ Poor English ver. ~

bleis-tift

July 27, 2014

Page 2: yield and return (poor English ver)

Self introduction

https://twitter.com/bleishttps://github.com/bleis-tift

Yuru-Fuwa F#er in Nagoya

I like static typed functional languages

Page 3: yield and return (poor English ver)

Agenda

Part1: Computation Expression

Part2:Di�erence of yield and return~considerations~

Part3:Di�erence of yield and return~implementations~

Part4:Conclusion

I will talk about how to implement computationexpression.

Page 4: yield and return (poor English ver)

Part1: Computation Expression

Page 5: yield and return (poor English ver)

Computation expression is...

expression that extends normal F# grammer andprovides some customize points

able to de�ne an user de�ne 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

}

Page 6: yield and return (poor English ver)

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.

Page 7: yield and return (poor English ver)

The way of implementation

The computation exprs are implemented bysome translation rules in F#

Needless any interfacesMost important thing is which translated exprs arecompilable

.

.grammer of computation expr

translatenormal grammer

Let's look at some translation rules together!

Page 8: yield and return (poor English ver)

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)

Page 9: yield and return (poor English ver)

Translation rule (most outside)

.

. builder-expr { cexpr }

F#compiler translates following:.

. let b = builder-expr in {| cexpr |}

b is a fresh variable.

Page 10: yield and return (poor English ver)

builder-expr

Just a normal expression

Builder is evaluated only onceDe�ne methods called at runtime into buildertype

All methods are de�ned as instance method

Page 11: yield and return (poor English ver)

{| ... |}

Translate expr to core language grammer

ex) {| cexpr |} ... translate cexpr

See below for further detais

Page 12: yield and return (poor English ver)

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

Page 13: yield and return (poor English ver)

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 translateit

Page 14: yield and return (poor English ver)

T -notation of {| cexpr |}

.T -notation..

. {| cexpr |} ≡ T (cexpr, λv.v)

λv.v is anonymous functionbefore dot: the parameterafter dot: the function body

v is the translated expression

Function application is done at compile-time(not run-time)

Page 15: yield and return (poor English ver)

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!

Page 16: yield and return (poor English ver)

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)

Page 17: yield and return (poor English ver)

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()

Page 18: yield and return (poor English ver)

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)))

Page 19: yield and return (poor English ver)

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)))

Page 20: yield and return (poor English ver)

Feature of computation expr

Computation expr is similar to

do notation (Haskell)

for expression (Scala)

query expression (C#)

Di�erence is computation expr has more �exibilitythan core language.

Computation expr is more powerful and friendly!

Page 21: yield and return (poor English ver)

Part2:Di�erence of yield and return

~considerations~

Page 22: yield and return (poor English ver)

Trans rules of yield and return

.Trans rules..

.

T (yield e, C) = C(b.Yield(e))

T (return e, C) = C(b.Return(e))

Di�erent point is only method...Today's main theme:

Why exist the same rules?

Page 23: yield and return (poor English ver)

Use properly...?

use yield for yield-like and use return forreturn-like...?

use yield for collection-like, otherwise usesreturn...?

What's the xxx-like!I want to decide clearly.

Page 24: yield and return (poor English ver)

Thinking about di�erence between yield

and return

Re�er the dictionary:

yield produce/provide

return give back

"return" should not be continue the followingprocess.Monad's return? I don't know:)

Page 25: yield and return (poor English ver)

Di�erence between yield and return

.yield..

.

list {

yield 1

printfn "done"

}

.return..

.

list {

return 1

printfn "done"

}

Whether or not to print "done"

Page 26: yield and return (poor English ver)

The case of C#

returnIE<T>

yield returnyield break

query expressionselect

I want to realize something like "yield return" and"yield break".

Page 27: yield and return (poor English ver)

seq expression

"return" is not supported

Di�cult for "yield break" like C#

Let's reimplements seq expression by computationexpression!

Page 28: yield and return (poor English ver)

Part3:Di�erence of yield and return

~implementations~

Page 29: yield and return (poor English ver)

Problem

The trans rule is same yield and return...

Page 30: yield and return (poor English ver)

Plan 1

The focus on "return" breaks remained process

Need to return value when called "return"

Throw exception that wraps returning value inReturn method and catch the exception in Runmethod

Page 31: yield and return (poor English ver)

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

Page 32: yield and return (poor English ver)

Impl by exception

.Usage..

.

> seq2 { yield 1; yield 2 };;

val it : seq<int> = seq [1; 2]

> seq2 { return 1; return 2 };;

val it : seq<int> = seq [1]

Yes!

Page 33: yield and return (poor English ver)

Impl by exception

Scala uses exception for the part of implementsreturn and break

Looks like easy

But!

Page 34: yield and return (poor English ver)

Problem

.Bad Example..

.

> seq2 { yield 1; return 2; return 3 };;

val it : seq<int> = seq [2]

In C#:.C#..

.

IEnumerable<int> F() {

yield return 1;

yield break 2;

yield break 3; }

It returns the sequencce contains 1 and 2.

Page 35: yield and return (poor English ver)

Re�ne 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>()

Page 36: yield and return (poor English ver)

Impl by exception

If provide "try-with", need to catch ReturnExnin try-with and reraise it

Eventually, can't implement clearly

Disinclined for use to exception for control �ow

Could be realized at least

Page 37: yield and return (poor English ver)

Plan 2

Continue or not continue

Insert the judgement of whether to call the restprocess

Page 38: yield and return (poor English ver)

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()

Page 39: yield and return (poor English ver)

impl by state �eld

.Usage..

.

> seq2 { yield 1; yield 2 };;

val it : seq<int> = seq [1; 2]

> seq2 { return 1; return 2 };;

val it : seq<int> = seq [1]

> seq2 { yield 1; return 2; return 3 };;

val it : seq<int> = seq [1; 2]

Yes!

Page 40: yield and return (poor English ver)

impl by state �eld

simple

looks like easy

But!

Page 41: yield and return (poor English ver)

Problem

builder instance has state

use the same builder instance at the same time....

.

Thread A

seq2 {yield 1

; // Combineyield 2 // oops!

} // Run

val it : seq<int> = seq [1]

seq2.isExit

false

true

false

Thread B

seq2 {return 10

} // Run

Page 42: yield and return (poor English ver)

Re�ne version

.Builder..

.

type SeqBuilder() =(* ... *)

let seq2 () = SeqBuilder()

.Usage..

.

> seq2 () { yield 1; yield 2 };;val it : seq<int> = seq [1; 2]> seq2 () { return 1; return 2 };;val it : seq<int> = seq [1]> seq2 () { yield 1; return 2; return 3 };;val it : seq<int> = seq [1; 2]

Page 43: yield and return (poor English ver)

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...

Page 44: yield and return (poor English ver)

Plan 3

Problem: state sharingSolution: use the argument

Carry the state by the argument, and unwrap thestate in Run methodThe rest process is not called if the state is"Break" in Combine method

Page 45: yield and return (poor English ver)

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()

Page 46: yield and return (poor English ver)

Impl by state arg

.Usage..

.

> seq2 { yield 1; yield 2 };;

val it : seq<int> = seq [1; 2]

> seq2 { return 1; return 2 };;

val it : seq<int> = seq [1]

> seq2 { yield 1; return 2; return 3 };;

val it : seq<int> = seq [1; 2]

Yes!

Page 47: yield and return (poor English ver)

Impl by state arg

Symmetry of the return and yield became clear

The implementation is very complex

Looks like good.

Page 48: yield and return (poor English ver)

Comparison

.Impl by exception..

.

member this.Yield(x: 'T) = Seq.singleton xmember 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 <- trueSeq.singleton x

.Impl by state arg..

.member this.Yield(x) = Seq.singleton x, Continuemember this.Return(x) = Seq.singleton x, Break

Page 49: yield and return (poor English ver)

Plan 4

Impl of exception: use the exception to breakthe rest processIt is same to discard continuation

yield: call continuationreturn: discard continuation

Page 50: yield and return (poor English ver)

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 : seq<int> = seq [1; 2]

> seq2 { return 1; return 2 };;

val it : seq<int> = seq [1]

> seq2 { yield 1; return 2; return 3 };;

val it : seq<int> = seq [1; 2]

Page 51: yield and return (poor English ver)

Impl by continuation

Symmetry of return and yield is clearShortest but complex (and not de�ne the Bindmethod)

The state arg version too

Page 52: yield and return (poor English ver)

Speed Comparison

Write yield at 100,000 times and execute.

builder timeunsupported return 20.5msby exception 20.5msby state �eld 20.7msby state arg 21.2msby continuation 22.6msseq expr 1.18ms

The di�erence is less.But builer is slower than seq expr in the �rst place.

Page 53: yield and return (poor English ver)

Part4 : Conclusion

Page 54: yield and return (poor English ver)

Summary

The computation expression is powerful

"yield" and "return" have the same translationrule but the meaning is di�erent

The seq expression is not supported "return" →reimplementationImplementations:

by exceptionby state �eld (deprecated)by state argby continuation

Page 55: yield and return (poor English ver)

Impl status of some libraries

Design about "return" ex) seq/list/optionTarget libraries:

FSharpxExtCoreFSharpPlusBasis.Core

As of July 21, 2014

Page 56: yield and return (poor English ver)

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

Page 57: yield and return (poor English ver)

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)))))

Page 58: yield and return (poor English ver)

FSharpx

can't compile...

Page 59: yield and return (poor English ver)

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

Page 60: yield and return (poor English ver)

ExtCore

can't compile...

Page 61: yield and return (poor English ver)

ExtCore

The impl of Zero is bad..Implementation of Zero..

.member inline __.Zero () : unit option =

Some () // TODO : Should this be None?

comment...

Page 62: yield and return (poor English ver)

FSharpPlus

can't compile.

Not provide While

Better choice

Page 63: yield and return (poor English ver)

Basis.Core

can compile.

return false-like value.

Page 64: yield and return (poor English ver)

Impl status of some libraries

No Game!

Page 65: yield and return (poor English ver)

Rethink about di�erence yield and return

Very few libraries implement computation exprcorrectly

There is a problem to be solved before yield andreturn

Should we give a semantic di�erence really?Should give if you want to take advantage ofcomputation exprShould not give if you provide only Bind andReturn (like FSharpPlus)

Page 66: yield and return (poor English ver)

Rethink about computation expression

Should Yield and Return receive continuation?Compile-time translation is e�cient

Can implement yield and return by now rulesI want to take this �exibility

Page 67: yield and return (poor English ver)

Suggestion of a Policy

The considered separately depending on the librarydesign

Case 1: provide monad/monad plus

Case 2: provide more general computing

Case 3: use computaion expr other than monad

Page 68: yield and return (poor English ver)

Provide monad/monad plus

Provide monadRequired: Bind/ReturnOptional: ReturnFrom (for convinience)Optional: Run

Provide another builder that unwrap the value

Provide monad plusRequired: Bind/Return/Zero/CombineZero is mzero, Combine is mplusRequired: Delay (depends on trans rule ofCombine)

member this.Delay(f) = f ()

Page 69: yield and return (poor English ver)

Provide more general computing

Separate the modules by featureBuilder module for providing Bind/ReturnBuilder module for providing Bind/Return/Comine

Combine is not mplus. Combine + Delay ismplus.

Inevitably, required Delay/Runmember this.Delay(f) = f

member this.Run(f) = f ()

Optional: ZeroSupport if-expr without else-clause

Page 70: yield and return (poor English ver)

Use computaion expr other than monad

I have no comments:)

If provide Combine, think about yield and return

Use CustomOperation if necessary

Page 71: yield and return (poor English ver)

Tasks

Report the bug to FSharpx and ExtCore

Create a library that is divided the module byfeature

Verify builder

Edi�cation

Page 72: yield and return (poor English ver)

Thanks.