yield and return (poor english ver)

72

Upload: bleis-tift

Post on 30-Jun-2015

371 views

Category:

Technology


1 download

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.