yield and return (poor English ver)

956 views

Published on

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
956
On SlideShare
0
From Embeds
0
Number of Embeds
77
Actions
Shares
0
Downloads
6
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

yield and return (poor English ver)

  1. 1. yield and return ∼ Poor English ver. ∼ bleis-tift July 27, 2014
  2. 2. Self introduction https://twitter.com/bleis https://github.com/bleis-tift Yuru-Fuwa F#er in Nagoya I like static typed functional languages
  3. 3. 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.
  4. 4. Part1: Computation Expression
  5. 5. 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 }
  6. 6. 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.
  7. 7. 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!
  8. 8. 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)
  9. 9. Translation rule (most outside) . . builder-expr { cexpr } F#compiler translates following: . . let b = builder-expr in {| cexpr |} b is a fresh variable.
  10. 10. 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
  11. 11. {| ... |} Translate expr to core language grammer ex) {| cexpr |} ... translate cexpr See below for further detais
  12. 12. 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
  13. 13. 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
  14. 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. 15. 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!
  16. 16. 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)
  17. 17. 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()
  18. 18. 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)))
  19. 19. 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)))
  20. 20. 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!
  21. 21. Part2:Dierence of yield and return ∼considerations∼
  22. 22. 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?
  23. 23. 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.
  24. 24. 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:)
  25. 25. Dierence between yield and return . yield .. . list { yield 1 printfn done } .return .. . list { return 1 printfn done } Whether or not to print done
  26. 26. The case of C return IET yield return yield break query expression select I want to realize something like yield return and yield break.
  27. 27. seq expression return is not supported Dicult for yield break like C# Let's reimplements seq expression by computation expression!
  28. 28. Part3:Dierence of yield and return ∼implementations∼
  29. 29. Problem The trans rule is same yield and return...
  30. 30. 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
  31. 31. 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
  32. 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. 33. Impl by exception Scala uses exception for the part of implements return and break Looks like easy But!
  34. 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. 35. 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()
  36. 36. 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
  37. 37. Plan 2 Continue or not continue Insert the judgement of whether to call the rest process
  38. 38. 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()
  39. 39. 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!
  40. 40. impl by state eld simple looks like easy But!
  41. 41. 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
  42. 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. 43. 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...
  44. 44. 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
  45. 45. 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()
  46. 46. 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!
  47. 47. Impl by state arg Symmetry of the return and yield became clear The implementation is very complex Looks like good.
  48. 48. 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
  49. 49. 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
  50. 50. 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]
  51. 51. Impl by continuation Symmetry of return and yield is clear Shortest but complex (and not dene the Bind method) The state arg version too
  52. 52. 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.
  53. 53. Part4 : Conclusion
  54. 54. 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
  55. 55. Impl status of some libraries Design about return ex) seq/list/option Target libraries: FSharpx ExtCore FSharpPlus Basis.Core As of July 21, 2014
  56. 56. 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
  57. 57. 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)))))
  58. 58. FSharpx can't compile...
  59. 59. 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
  60. 60. ExtCore can't compile...
  61. 61. ExtCore The impl of Zero is bad. . Implementation of Zero .. . member inline __.Zero () : unit option = Some () // TODO : Should this be None? comment...
  62. 62. FSharpPlus can't compile. Not provide While Better choice
  63. 63. Basis.Core can compile. return false-like value.
  64. 64. Impl status of some libraries No Game!
  65. 65. 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)
  66. 66. 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
  67. 67. 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
  68. 68. 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 ()
  69. 69. 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
  70. 70. Use computaion expr other than monad I have no comments:) If provide Combine, think about yield and return Use CustomOperation if necessary
  71. 71. Tasks Report the bug to FSharpx and ExtCore Create a library that is divided the module by feature Verify builder Edication
  72. 72. Thanks.

×