Slideshare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our User Agreement and Privacy Policy.

Slideshare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our Privacy Policy and User Agreement for details.

Successfully reported this slideshow.

Like this presentation? Why not share!

- Scala in practice by Tomer Gabel 22242 views
- Scala profiling by Filippo Pacifici 13226 views
- Scala Talk at FOSDEM 2009 by Martin Odersky 58244 views
- Scala test by Inphina Technologies 6406 views
- Metaprogramming in Scala 2.10, Eug... by Vasil Remeniuk 7494 views
- Scalding: Twitter's Scala DSL for H... by johnynek 12779 views

15,241 views

Published on

1) The ability to reorder computation flow, making the program implicitly parallelisable. Modern imperative language compilers, even using careful synchronization of concurrent code, still generate huge chunks of sequential instructions that need to be executed on a single processor core; a purely functional language compilers can dispatch very small chunks to many (hundreds and thousands) of cores, carefully eliminating as many execution path dependencies as possible.

2) As the compiler formalizes different types of side effects, it can detect a whole new class of program errors at compile time, including resource acquisition and releasing problems, concurrent access to shared resources, many types of deadlocks etc. It is not yet a full-fledged program verification, but it is a big step in that direction.

Scala is a semi-imperative language with strong support for functional programming and rich type system. One can isolate the purely functional core of the language which can be put on the firm mathematical foundation of dependent type theories. We argue that it is possible to treat Scala code as it's written by now as an implicit do-notation which can be then reduced to a purely functional core by means of recently introduced Scala macros. The formalism of arrows and applicative contexts can bring Scala to a full glory of an implicitly parallelisable programming language, while still keeping its syntax mostly unchanged.

No Downloads

Total views

15,241

On SlideShare

0

From Embeds

0

Number of Embeds

6,254

Shares

0

Downloads

123

Comments

0

Likes

17

No embeds

No notes for slide

- 1. Procedure Typing for Scala Procedure Typing for Scala Alexander Kuklev∗ , Alexander Temerev‡ * Institute of Theoretical Physics, University of Göttingen ‡ Founder and CEO at Miriamlaurel Sàrl, Geneva April 10, 2012
- 2. Procedure Typing for Scala Functions and procedures In programming we have: – pure functions; – functions with side eﬀects (AKA procedures).
- 3. Procedure Typing for Scala Functions and procedures In programming we have: – pure functions; – functions with side eﬀects (AKA procedures). Scala does not diﬀerentiate between them: – both have types A => B .
- 4. Procedure Typing for Scala But it should!
- 5. Procedure Typing for Scala But it should! Static side eﬀect tracking enables – implicit parallelisability;
- 6. Procedure Typing for Scala But it should! Static side eﬀect tracking enables – implicit parallelisability; – compile-time detection of a whole new class of problems: (resource acquisition and releasing problems, race conditions, deadlocks, etc.).
- 7. Procedure Typing for Scala Short list of applicable methodologies: Kleisli Arrows of Outrageous Fortune (2011, C. McBride) Capabilities for Uniqueness and Borrowing (2010, P. Haller, M. Odersky) Static Detection of Race Conditions [..] (2010, M. Christakis, K. Sagonas) Static Deadlock Detection [..] (2009, F. de Boer, I. Grabe,M. Steﬀen) Complete Behavioural Testing of Object-Oriented Systems using CCS-Augmented X-Machines (2002, M. Stannett, A. J. H. Simons) An integration testing method that is proved to ﬁnd all faults (1997, F. Ipate, M. Holcombe)
- 8. Procedure Typing for Scala Specifying procedure categories We propose a new syntax where a function deﬁnition may include a category it belongs to: A =>[Pure] B – pure functions; A =>[Proc] B – procedures.
- 9. Procedure Typing for Scala Specifying procedure categories There’s a lot more than Pure and Proc There is a whole lattice of categories between Pure and Proc : Logged: procedures with no side eﬀects besides logging; Throws[E]: no side eﬀects besides throwing exceptions of type E ; Reads(ﬁle): no side eﬀects besides reading the file ; etc.
- 10. Procedure Typing for Scala Specifying procedure categories Extensible approach An eﬀect system should be extensible. ⇒ We must provide a way to deﬁne procedure categories. Procedure categories are binary types like Function[_,_] or Logged[_,_] 1 1 Deﬁnition of parameterized categories, e.g. Throws[E] or Reads(resource), is also possible with the help of type lambdas and/or type providers.
- 11. Procedure Typing for Scala Specifying procedure categories Extensible approach An eﬀect system should be extensible. ⇒ We must provide a way to deﬁne procedure categories. Procedure categories are binary types like Function[_,_] or Logged[_,_] 1 equipped with some additional structure using an associated type class. 1 Deﬁnition of parameterized categories, e.g. Throws[E] or Reads(resource), is also possible with the help of type lambdas and/or type providers.
- 12. Procedure Typing for Scala Specifying procedure categories Extensible approach Syntax details – A =>[R] B R[A,B] – A => B Function[A,B] , i.e. type named “Function” from the local context, not necessarily the Function from Predef2 . 2 (A, B) should also mean Pair[A,B] from the local context, as they must be consistent with functions: (A, B) => C ∼ A => B => C . =
- 13. Procedure Typing for Scala Specifying procedure categories Extensible approach Proposed syntax for deﬁnitions def process(d: Data): =>[Throws[InterruptedException]] Int = { ... // Procedure types can be dependent def copy(src: File, dest: File): =>[Reads(src), Writes(dest)] { ... // Pre- and postconditions can be treated as effects too: def open(file: File): =>[Pre{file@Closed}, Post{file@Open}] { ... Last two examples rely on recently added dependent method types. (N.B. Such stunts are hard to implement using type-and-eﬀect systems.)
- 14. Procedure Typing for Scala Deﬁning procedure categories How to deﬁne a procedure category?
- 15. Procedure Typing for Scala Deﬁning procedure categories First of all, it should be a category in the usual mathematical sense, i.e. we have to provide procedure composition and its neutral. trait Category[Function[_,_]] { def id[T]: T => T def compose[A, B, C](f: B => C, g: A => B): A => C }
- 16. Procedure Typing for Scala Deﬁning procedure categories To give an example, let’s model logged functions on pure functions: type Logged[A, B] = (A =>[Pure] (B, String)) object Logged extends Category[Logged] { def id[T] = {x: T => (x, "")} def compose[A, B, C](f: B => C, g: A => B) = {x: A => val (result1, logOutput1) = g(x) val (result2, logOutput2) = f(result1) (result2, logOutput1 + logOutput2) } } Besides their results, logged functions produce log output of type String. Composition of logged functions concatenates their logs.
- 17. Procedure Typing for Scala Deﬁning procedure categories Linear functional composition is not enough. We want to construct arbitrary circuits. (This is the key step in enabling implicit parallelisability.)
- 18. Procedure Typing for Scala Deﬁning procedure categories To make arbitrary circuits, we need just one additional operation besides composition: def affix[A, B, C, D](f: A => B, g: C => D): (A, C) => (B, D)
- 19. Procedure Typing for Scala Deﬁning procedure categories In case of pure functions, affix is trivial: – the execution of f and g is independent. In case of procedures affix is not-so-trivial: – have to pass the eﬀects of f to the execution context of g ; – execution order can be signiﬁcant.
- 20. Procedure Typing for Scala Deﬁning procedure categories Thus, procedures belong to a stronger structure than just a category, namely a structure embracing the aﬃx operation. Such a structure is called circuitry.
- 21. Procedure Typing for Scala Deﬁning procedure categories A circuitry is a closed monoidal category with respect to the affix operation, where affix splits as follows: trait Circuitry[F[_,_]] extends PairCategory[F] { def passr[A, B, C](f: A => B): (A, C) => (B, C) def passl[B, C, D](g: C => D): (B, C) => (B, D) override def affix[A, B, C, D](f: A => B, g: C => D) = { compose(passl(g), passr(f)) } } + =
- 22. Procedure Typing for Scala Deﬁning procedure categories For the mathematicians among us: trait PairCategory[F[_,_]] extends Category[F] { type Pair[A, B] def assoc[X, Y, Z]: ((X, Y), Z) => (X, (Y, Z)) def unassoc[X, Y, Z]: (X, (Y, Z)) => ((X, Y), Z) type Unit def cancelr[X]: (X, Unit) => X def cancell[X]: (Unit, X) => X def uncancelr[X]: X => (X, Unit) def uncancell[X]: X => (Unit, X) def curry[A, B, C](f: (A, B) => C): A => B => C def uncurry[A, B, C](f: A => B => C): (A, B) => C def affix[A, B, C, D](f: A => B, g: C => D): (A, B) => (C, D) }
- 23. Procedure Typing for Scala Deﬁning procedure categories For the mathematicians among us: trait PairCategory[F[_,_]] extends Category[F] { type Pair[A, B] def assoc[X, Y, Z]: ((X, Y), Z) => (X, (Y, Z)) def unassoc[X, Y, Z]: (X, (Y, Z)) => ((X, Y), Z) type Unit def cancelr[X]: (X, Unit) => X def cancell[X]: (Unit, X) => X def uncancelr[X]: X => (X, Unit) def uncancell[X]: X => (Unit, X) def curry[A, B, C](f: (A, B) => C): A => B => C def uncurry[A, B, C](f: A => B => C): (A, B) => C def affix[A, B, C, D](f: A => B, g: C => D): (A, B) => (C, D) } Don’t panic!
- 24. Procedure Typing for Scala Deﬁning procedure categories For the mathematicians among us: trait PairCategory[F[_,_]] extends Category[F] { type Pair[A, B] def assoc[X, Y, Z]: ((X, Y), Z) => (X, (Y, Z)) def unassoc[X, Y, Z]: (X, (Y, Z)) => ((X, Y), Z) type Unit def cancelr[X]: (X, Unit) => X def cancell[X]: (Unit, X) => X def uncancelr[X]: X => (X, Unit) def uncancell[X]: X => (Unit, X) def curry[A, B, C](f: (A, B) => C): A => B => C def uncurry[A, B, C](f: A => B => C): (A, B) => C def affix[A, B, C, D](f: A => B, g: C => D): (A, B) => (C, D) } Don’t panic! In most cases the default Pair and Unit work perfectly well. ⇒ No need to understand any of this, just use with Cartesian .
- 25. Procedure Typing for Scala Deﬁning procedure categories Elements of circuitries are called generalised arrows. Besides procedures, circuitries provide a common formalism for: – reversible quantum computations; – electrical and logical circuits; – linear and aﬃne logic; – actor model and other process calculi. Circuitries provide the most general formalism for computations, see “Multi-Level Languages are Generalized Arrows”, A. Megacz.
- 26. Procedure Typing for Scala Deﬁning procedure categories We are talking mostly about procedure typing, so we are going to consider some special cases: Arrow circuitries3 : circuitries generalising =>[Pure] . Executable categories: categories generalising to =>[Proc] . Procedure categories: executable cartesian4 procedure circuitries. 3 AKA plain old “arrows” in Haskell and scalaz. 4 i.e. having cartesian product types.
- 27. Procedure Typing for Scala Deﬁning procedure categories trait ArrowCircuitry[F[_,_]] extends Circuitry[F] { def reify[A, B](f: A =>[Pure] B): A => B ... // With reify we get id and passl for free } trait Executable extends Category[_] { def eval[A, B](f: A => B): A =>[Proc] B // eval defines the execution strategy } trait ProcCategory[F[_,_]] extends ArrowCircuitry[F] with Executable with Cartesian { ... // Some additional goodies }
- 28. Procedure Typing for Scala Deﬁning procedure categories It’s time to give a full deﬁnition of =>[Logged] : type Logged[A, B] = (A =>[Pure] (B, String)) object LoggedCircuitryImpl extends ProcCategory[Logged] { def reify[A, B](f: A =>[Pure] B) = {x: A => (f(x), "")} def compose[A, B, C](f: B => C, g: A => B) = {x: A => val (result1, logOutput1) = g(x) val (result2, logOutput2) = f(result1) (result2, logOutput1 + logOutput2) } def passr[A, B, C](f: A => B): = {x : (A, C) => val (result, log) = f(x._1) ((result, x._2), log) } def eval[A, B](p: A => B) = {x: A => val (result, log) = p(x) println(log); result } }
- 29. Procedure Typing for Scala Deﬁning procedure categories It’s time to give a full deﬁnition of =>[Logged] : type Logged[A, B] = (A =>[Pure] (B, String)) object LoggedCircuitryImpl extends ProcCategory[Logged] { def reify[A, B](f: A =>[Pure] B) = {x: A => (f(x), "")} def compose[A, B, C](f: B => C, g: A => B) = {x: A => val (result1, logOutput1) = g(x) val (result2, logOutput2) = f(result1) (result2, logOutput1 + logOutput2) } def passr[A, B, C](f: A => B): = {x : (A, C) => val (result, log) = f(x._1) ((result, x._2), log) } def eval[A, B](p: A => B) = {x: A => val (result, log) = p(x) println(log); result } } Wasn’t that easy?
- 30. Procedure Typing for Scala Deﬁning procedure categories Additionally we need a companion object for Logged[_,_] type. That’s where circuitry-speciﬁc primitives should be deﬁned. object Logged { val log: Logged[Unit, Unit] = {s: String => ((),s)} }
- 31. Procedure Typing for Scala Deﬁning procedure categories Other circuitry-speciﬁc primitives include: – throw and catch for =>[Throws[E]] – shift and reset for =>[Cont] – match/case and if/else for =>[WithChoice] – while and recursion for =>[WithLoops] – etc. Often they have to be implemented with Scala macros (available in a next major Scala release near you).
- 32. Procedure Typing for Scala Language puriﬁcation by procedure typing Note that impure code is localised to the eval method. Thus, thorough usage of procedure typing localizes impurities to well-controlled places in libraries. Except for these, Scala becomes a clean multilevel language, with eﬀective type systems inside blocks being type-and-eﬀect systems internal to corresponding circuitries.
- 33. Procedure Typing for Scala Language puriﬁcation by procedure typing Curry-Howard-Lambek correspondence relates type theories, logics and categories: For cartesian closed categories: Internal logic = constructive proposition logic Internal language = simply-typed λ-calculus For locally cartesian closed categories: Internal logic = constructive predicate logic Internal language = dependently-typed λ-calculus ...
- 34. Procedure Typing for Scala Language puriﬁcation by procedure typing Curry-Howard-Lambek correspondence relates type theories, logics and categories: For cartesian closed categories: Internal logic = constructive proposition logic Internal language = simply-typed λ-calculus For locally cartesian closed categories: Internal logic = constructive predicate logic Internal language = dependently-typed λ-calculus ... Informally, the work of A. Megacz provides an extension of it: For arrow circuitries: Internal logics = contextual logics Internal languages = type-and-eﬀect extended λ-calculi
- 35. Procedure Typing for Scala Language puriﬁcation by procedure typing Scala puriﬁcation/modularization programme – Design a lattice of procedure categories between Pure and Proc . In particular, reimplement ﬂow control primitives as macro5 methods in companion objects of respective categories. 5 One reason for employing macros is to guarantee that scaﬀoldings will be completely removed in compile time with no overhead on the bytecode level.
- 36. Procedure Typing for Scala Language puriﬁcation by procedure typing Scala puriﬁcation/modularization programme – Design a lattice of procedure categories between Pure and Proc . In particular, reimplement ﬂow control primitives as macro5 methods in companion objects of respective categories. – Implement rules for lightweight eﬀect polymorphism using a system of implicits à la Rytz-Odersky-Haller (2012) 5 One reason for employing macros is to guarantee that scaﬀoldings will be completely removed in compile time with no overhead on the bytecode level.
- 37. Procedure Typing for Scala Language puriﬁcation by procedure typing Scala puriﬁcation/modularization programme – Design a lattice of procedure categories between Pure and Proc . In particular, reimplement ﬂow control primitives as macro5 methods in companion objects of respective categories. – Implement rules for lightweight eﬀect polymorphism using a system of implicits à la Rytz-Odersky-Haller (2012) – Retroﬁt Akka with circuitries internalizing an appropriate actor calculus + ownership/borrowing system (Haller, 2010). 5 One reason for employing macros is to guarantee that scaﬀoldings will be completely removed in compile time with no overhead on the bytecode level.
- 38. Procedure Typing for Scala Comparing with other notions of computation How do circuitries compare to other notions of computation?
- 39. Procedure Typing for Scala Comparing with other notions of computation Type-and-eﬀect systems Type-and-eﬀect systems are the most well studied approach to procedure typing:
- 40. Procedure Typing for Scala Comparing with other notions of computation Type-and-eﬀect systems Type-and-eﬀect systems are the most well studied approach to procedure typing: eﬀects are speciﬁers as annotations for “functions”; type system is extended with rules for “eﬀects”.
- 41. Procedure Typing for Scala Comparing with other notions of computation Type-and-eﬀect systems Type-and-eﬀect systems are the most well studied approach to procedure typing: eﬀects are speciﬁers as annotations for “functions”; type system is extended with rules for “eﬀects”. Circuitry formalism is not an alternative, but an enclosure for them.
- 42. Procedure Typing for Scala Comparing with other notions of computation Type-and-eﬀect systems Direct implementation of type-and-eﬀect systems – is rigid (hardly extensible) and – requires changes to the typechecker.
- 43. Procedure Typing for Scala Comparing with other notions of computation Type-and-eﬀect systems Direct implementation of type-and-eﬀect systems – is rigid (hardly extensible) and – requires changes to the typechecker. Embedding eﬀects into the type system by means of the circuitry formalism resolves the issues above.
- 44. Procedure Typing for Scala Comparing with other notions of computation Monads Arrows generalise monads In Haskell, monads are used as basis for imperative programming, but they are often not general enough (see Hughes, 2000). Monads are similar to cartesian arrow circuitries. The only diﬀerence is that they are not equipped with non-linear composition.
- 45. Procedure Typing for Scala Comparing with other notions of computation Monads Monads – do not compose well, – prescribe rigid execution order, – are not general enough for concurrent computations.
- 46. Procedure Typing for Scala Comparing with other notions of computation Monads Monads – do not compose well, – prescribe rigid execution order, – are not general enough for concurrent computations. Circuitries were invented to cure this.
- 47. Procedure Typing for Scala Comparing with other notions of computation Applicatives Applicatives are a special case of arrows... If procedures of type =>[A] never depend on eﬀects of other procedures of the same type, A is called essentially commutative. Example =>[Reads(config), Writes(log), Throws[NonBlockingException]] Essentially commutative arrows arise from applicative functors. They are ﬂexible and easy to handle: you don’t have to propagate eﬀects, just accumulate them behind the scenes.
- 48. Procedure Typing for Scala Comparing with other notions of computation Applicatives ...but not a closed special case! Composing applicatives may produce non-commutative circuitries like =>[Reads(file), Writes(file)] . Procedures of this type are no longer eﬀect-independent: eﬀect of writes have to be passed to subsequent reads. Besides these, there are also inherently non-commutative arrows such as those arising from monads6 , comonads and Hoare triples7 . 6 e.g. Tx = transaction monad, Cont = continuation passing monad. 7 pre- and postconditioned arrows.
- 49. Procedure Typing for Scala Comparing with other notions of computation Traditional imperative approach Can we do everything available in imperative languages with arrows and circuitries?
- 50. Procedure Typing for Scala Comparing with other notions of computation Traditional imperative approach Can we do everything available in imperative languages with arrows and circuitries? Any imperative code can be reduced to compose and affix.
- 51. Procedure Typing for Scala Comparing with other notions of computation Traditional imperative approach Can we do everything available in imperative languages with arrows and circuitries? Any imperative code can be reduced to compose and affix. The reduction process is known as variable elimination, it can be understood as translation to a concatenative language like Forth.
- 52. Procedure Typing for Scala Comparing with other notions of computation Traditional imperative approach Can we do everything available in imperative languages with arrows and circuitries? Any imperative code can be reduced to compose and affix. The reduction process is known as variable elimination, it can be understood as translation to a concatenative language like Forth. (The concatenative languages’ juxtaposition is an overloaded operator reducing to either compose or affix depending on how operands’ types match.)
- 53. Procedure Typing for Scala Comparing with other notions of computation Traditional imperative approach Can we do everything available in imperative languages with arrows and circuitries? Any imperative code can be reduced to compose and affix. The reduction process is known as variable elimination, it can be understood as translation to a concatenative language like Forth. (The concatenative languages’ juxtaposition is an overloaded operator reducing to either compose or affix depending on how operands’ types match.) But! Writing code this way can be quite cumbersome.
- 54. Procedure Typing for Scala Do-notation Deﬁning procedure categories is easy enough. How about using them? We develop a quasi-imperative notation8 and implement it using macros. Our notation shares syntax with usual Scala imperative code... ...but has diﬀerent semantics: it compiles to a circuit of appropriate type instead of being executed immediately. 8 Akin to Haskell’s do-notation, but much easier to use.
- 55. Procedure Typing for Scala Do-notation Deﬁning procedure categories is easy enough. How about using them? We develop a quasi-imperative notation8 and implement it using macros. Our notation shares syntax with usual Scala imperative code... ...but has diﬀerent semantics: it compiles to a circuit of appropriate type instead of being executed immediately. Circuit notation for Scala is the topic of the part II... 8 Akin to Haskell’s do-notation, but much easier to use.
- 56. Procedure Typing for Scala Do-notation Do-notation example ...but here’s a small example to keep your interest Even pure functions have a side eﬀect: they consume time. =>[Future] is an example of a retroﬁtting procedure category9 . =>[Future] { val a = alpha(x) val b = beta(x) after (a | b) { Log.info("First one is completed") } after (a & b) { Log.info("Both completed") } gamma(a, b) } 9 its reify is a macro, so any procedures can be retroﬁtted to be =>[Future].
- 57. Procedure Typing for Scala Do-notation Literature: – The marriage of eﬀects and monads, P. Wadler, P. Thiemann – Generalising monads to arrows, J. Hughes – The Arrow Calculus, S. Lindley, P. Wadler, and J. Yallop – Categorical semantics for arrows, B. Jacobs et al. – What is a Categorical Model of Arrows?, R. Atkey – Parameterized Notions of Computation, R. Atkey – Multi-Level Languages are Generalized Arrows, A. Megacz
- 58. Procedure Typing for Scala Syntax for Circuitires Part II: Syntax for Circuitires A cup of coﬀee?
- 59. Procedure Typing for Scala Syntax for Circuitires Implicit Unboxing How do you use an arrow (say f: Logged[Int, String] ) in present Scala code? println(f(5)) seems to be the obvious way, but that’s impossible, application is not deﬁned for f. To facilitate such natural notation, we need implicit unboxing.
- 60. Procedure Typing for Scala Syntax for Circuitires Implicit Unboxing Preliminaries A wrapping is a type F[_] equipped with eval[T](v: F[T]): T and reify[T](expr: => T): F[T] (reify often being a macro) so that eval(reify(x)) ≡ x and reify(eval(x)) ≡ x for all x of the correct type.
- 61. Procedure Typing for Scala Syntax for Circuitires Implicit Unboxing Preliminaries A wrapping is a type F[_] equipped with eval[T](v: F[T]): T and reify[T](expr: => T): F[T] (reify often being a macro) so that eval(reify(x)) ≡ x and reify(eval(x)) ≡ x for all x of the correct type. A prototypical example where reify is a macro is Expr[T]. Example with no macros involved is Future[T] (with await as eval).
- 62. Procedure Typing for Scala Syntax for Circuitires Implicit Unboxing Preliminaries Implicit unboxing is this: whenever a value of the wrapping type F[T] is found where a value of type T is accepted, its eval is called implicitly. In homoiconic languages (including Scala), all expressions can be considered initially having the type Expr[T] and being unboxed into T by an implicit unboxing rule Expr[T] => T .
- 63. Procedure Typing for Scala Syntax for Circuitires Implicit Unboxing Syntax proposal Let’s introduce an instruction implicit[F] enabling implicit unboxing for F in its scope. Implicit contexts can be implemented using macros: – macro augments the relevant scope by F.reify as an implicit conversion from F[T] to T; – F.eval is applied to every occurrence of a symbol having or returning type F[T] which is deﬁned outside of its scope.
- 64. Procedure Typing for Scala Syntax for Circuitires Implicit Unboxing Code that uses futures and promises can be made much more readable by implicit unboxing. An example: dataﬂows in Akka 2.0. Presently they look like this: flow { z << (x() + y()) if (v() > u) println("z = " + z()) }
- 65. Procedure Typing for Scala Syntax for Circuitires Implicit Unboxing Now this can be recast without any unintuitive empty parentheses: flow { z << x + y if (v > u) println("z = " + z) }
- 66. Procedure Typing for Scala Syntax for Circuitires Implicit Unboxing Back to our Logged example: implicit[Logged] def example(f: Int =>[Logged] String, n: Int): List[String] { f(n).split(", ") } Which translates to: def example(f: Int =>[Logged] String, n: Int): List[String] { LoggedCircuitryImpl.eval(f)(n).split(", ") }
- 67. Procedure Typing for Scala Syntax for Circuitires Purifying Scala Now, which procedure category should example() belong to? As it evaluates =>[Logged], it should be =>[Logged] itself. This allows its reinterpretation without any usage of eval: def example(f: Int =>[Logged] String, n: Int): List[String] = { import LoggedCircuitryImpl._ reify{n} andThen f andThen reify{_.split(", ")} } This is now a pure code generating a new circuit of the type =>[Logged] based on the existing one (f) and some pure functions.
- 68. Procedure Typing for Scala Syntax for Circuitires Purifying Scala Purity Declaration Let’s introduce @pure annotation which explicitly forbids calling any functions with side eﬀects and assignments of foreign variables. This renders the code pure. Procedure with side eﬀects have to be composed by circuit composition operations which are pure. The execution of procedures, which is impure, always lies outside of the scope. All code examples below are to be read as @pure .
- 69. Procedure Typing for Scala Syntax for Circuitires Purifying Scala Inside of @pure implicit unboxing for arrows becomes implicit circuit notation, which is operationally indistinguishable, but semantically diﬀerent.
- 70. Procedure Typing for Scala Syntax for Circuitires Circuit notation Circuit notation, general idea: – write circuitry type like =>[X] in front of a braced code block; – the code block will be reinterpreted as a circuitry of the given type (via macros).
- 71. Procedure Typing for Scala Syntax for Circuitires Circuit notation Example: =>[Logged] { f(x) + g(x) } Result: (reify{x} andThen reify(dup) andThen (f affix g) andThen reify{_ + _})
- 72. Procedure Typing for Scala Syntax for Circuitires Circuit notation In presence of implicit[X] every free braced block {...} which uses external symbols of the type =>[X] should be treated as =>[X] {...} , an implicit form of circuit syntax.
- 73. Procedure Typing for Scala Syntax for Circuitires Circuit notation The desugaring rules producing operationally indistinguishable circuits from imperative-style code blocks are quite complicated, but certainly doable. To make the other direction possible, we need an additional operator: after .
- 74. Procedure Typing for Scala Syntax for Circuitires Circuit notation Consider two arrows f: Unit => Unit and g: Unit => Unit . They can be composed in two ways: f affix g (out-of-order) and f andThen g (in-order). affix in circuit notation will obviously look like f; g , though for andThen we need some new syntax: =>[Future] { val n = f after(n) g }
- 75. Procedure Typing for Scala Syntax for Circuitires Circuit notation Without after , =>[Future] and other similar circuitries respect only dataﬂow ordering, but ignore the order of independent eﬀects (e.g. writing into a log). By combining usual imperative notation and after , any possible circuit conﬁgurations can be achieved.
- 76. Procedure Typing for Scala Syntax for Circuitires Circuit notation Now the example stated above is fully understandable: =>[Future] { val a = alpha(x) val b = beta(x) after (a | b) { Log.info("First one is completed") } after (a & b) { Log.info("Both completed") } gamma(a, b) } ( after trivially supports any combinations of ands and ors.)
- 77. Procedure Typing for Scala Syntax for Circuitires Circuit notation Blocks as Objects For the sake of composability, blocks should be treated as anonymous classes extending their arrow type: =>[Future] { val result = { @expose val partialResult = compute1(x) compute2(partialResult) } after (result.partialResult) { Log.info("Partial result ready") } } The result in the after context is not just =>[Future] Int , but its anonymous descendant with a public member partialResult .
- 78. Procedure Typing for Scala Syntax for Circuitires Circuit notation Of course, it should also work for named blocks: def lengthyComputation(x: Double): Double = { var _progress = 0.0 // goes from 0.0 to 1.0 @expose def progress = _progress // public getter ... // _progress is updated when necessary } val f = future someLengthyCalculation(x) while (!f.isDone) { Log.info("Progress: " + f.progress) wait(500 ms) } (This is a perfect example of what can easily be done with macros.)
- 79. Procedure Typing for Scala Syntax for Circuitires Circuit notation The exact desugaring rules are quite complex (but perfectly real). We hope these examples gave you some insight how everything might work. Thank you!

No public clipboards found for this slide

Be the first to comment