procedure typing for scala

79
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

Upload: akuklev

Post on 10-May-2015

12.795 views

Category:

Technology


2 download

DESCRIPTION

The great attractiveness of purely functional languages is their ability to depart from sequential order of computation. Theoretically, it enables two important features of the compiler: 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.

TRANSCRIPT

Page 1: Procedure Typing for Scala

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

Page 2: Procedure Typing for Scala

Procedure Typing for Scala

Functions and procedures

In programming we have:– pure functions;– functions with side effects (AKA procedures).

Scala does not differentiate between them:– both have types A => B .

Page 3: Procedure Typing for Scala

Procedure Typing for Scala

Functions and procedures

In programming we have:– pure functions;– functions with side effects (AKA procedures).

Scala does not differentiate between them:– both have types A => B .

Page 4: Procedure Typing for Scala

Procedure Typing for Scala

But it should!

Static side effect tracking enables– implicit parallelisability;

– compile-time detection of a whole new class of problems:(resource acquisition and releasing problems, race conditions,deadlocks, etc.).

Page 5: Procedure Typing for Scala

Procedure Typing for Scala

But it should!

Static side effect tracking enables– implicit parallelisability;

– compile-time detection of a whole new class of problems:(resource acquisition and releasing problems, race conditions,deadlocks, etc.).

Page 6: Procedure Typing for Scala

Procedure Typing for Scala

But it should!

Static side effect tracking enables– implicit parallelisability;– compile-time detection of a whole new class of problems:

(resource acquisition and releasing problems, race conditions,deadlocks, etc.).

Page 7: Procedure Typing for Scala

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

Complete Behavioural Testing of Object-Oriented Systems usingCCS-Augmented X-Machines (2002, M. Stannett, A. J. H. Simons)

An integration testing method that is proved to find all faults(1997, F. Ipate, M. Holcombe)

Page 8: Procedure Typing for Scala

Procedure Typing for ScalaSpecifying procedure categories

We propose a new syntax

where a function definition may include a category it belongs to:

A =>[Pure] B – pure functions;A =>[Proc] B – procedures.

Page 9: Procedure Typing for Scala

Procedure Typing for ScalaSpecifying procedure categoriesThere’s a lot more than Pure and Proc

There is a whole lattice of categories between Pure and Proc :

Logged: procedures with no side effects besides logging;Throws[E]: no side effects besides throwing exceptions of type E ;Reads(file): no side effects besides reading the file ;

etc.

Page 10: Procedure Typing for Scala

Procedure Typing for ScalaSpecifying procedure categoriesExtensible approach

An effect system should be extensible.⇒ We must provide a way to define procedure categories.

Procedure categories are binary types like Function[_,_] orLogged[_,_] 1

equipped with some additional structure using anassociated type class.

1Definition of parameterized categories, e.g. Throws[E] or Reads(resource),is also possible with the help of type lambdas and/or type providers.

Page 11: Procedure Typing for Scala

Procedure Typing for ScalaSpecifying procedure categoriesExtensible approach

An effect system should be extensible.⇒ We must provide a way to define procedure categories.

Procedure categories are binary types like Function[_,_] orLogged[_,_] 1 equipped with some additional structure using anassociated type class.

1Definition of parameterized categories, e.g. Throws[E] or Reads(resource),is also possible with the help of type lambdas and/or type providers.

Page 12: Procedure Typing for Scala

Procedure Typing for ScalaSpecifying procedure categoriesExtensible approach

Syntax details

– A =>[R] B R[A,B]

– A => B Function[A,B] , i.e. type named “Function” from thelocal 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 .

Page 13: Procedure Typing for Scala

Procedure Typing for ScalaSpecifying procedure categoriesExtensible approach

Proposed syntax for definitions

def process(d: Data):=>[Throws[InterruptedException]] Int = { ...

// Procedure types can be dependentdef 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-effect systems.)

Page 14: Procedure Typing for Scala

Procedure Typing for ScalaDefining procedure categories

How to define a procedure category?

Page 15: Procedure Typing for Scala

Procedure Typing for ScalaDefining 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 => Tdef compose[A, B, C](f: B => C, g: A => B): A => C

}

Page 16: Procedure Typing for Scala

Procedure Typing for ScalaDefining 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 typeString. Composition of logged functions concatenates their logs.

Page 17: Procedure Typing for Scala

Procedure Typing for ScalaDefining procedure categories

Linear functional composition is not enough.We want to construct arbitrary circuits.

(This is the key step in enabling implicit parallelisability.)

Page 18: Procedure Typing for Scala

Procedure Typing for ScalaDefining procedure categories

To make arbitrary circuits, we need just one additional operationbesides composition:

def affix[A, B, C, D](f: A => B, g: C => D): (A, C) => (B, D)

Page 19: Procedure Typing for Scala

Procedure Typing for ScalaDefining 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 effects of f to the execution context of g ;– execution order can be significant.

Page 20: Procedure Typing for Scala

Procedure Typing for ScalaDefining procedure categories

Thus, procedures belong to a stronger structure than just acategory, namely a structure embracing the affix operation.

Such a structure is called circuitry.

Page 21: Procedure Typing for Scala

Procedure Typing for ScalaDefining 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))

}}

+ =

Page 22: Procedure Typing for Scala

Procedure Typing for ScalaDefining 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 Unitdef cancelr[X]: (X, Unit) => Xdef cancell[X]: (Unit, X) => Xdef uncancelr[X]: X => (X, Unit)def uncancell[X]: X => (Unit, X)

def curry[A, B, C](f: (A, B) => C): A => B => Cdef uncurry[A, B, C](f: A => B => C): (A, B) => Cdef 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 .

Page 23: Procedure Typing for Scala

Procedure Typing for ScalaDefining 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 Unitdef cancelr[X]: (X, Unit) => Xdef cancell[X]: (Unit, X) => Xdef uncancelr[X]: X => (X, Unit)def uncancell[X]: X => (Unit, X)

def curry[A, B, C](f: (A, B) => C): A => B => Cdef uncurry[A, B, C](f: A => B => C): (A, B) => Cdef 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 .

Page 24: Procedure Typing for Scala

Procedure Typing for ScalaDefining 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 Unitdef cancelr[X]: (X, Unit) => Xdef cancell[X]: (Unit, X) => Xdef uncancelr[X]: X => (X, Unit)def uncancell[X]: X => (Unit, X)

def curry[A, B, C](f: (A, B) => C): A => B => Cdef uncurry[A, B, C](f: A => B => C): (A, B) => Cdef 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 .

Page 25: Procedure Typing for Scala

Procedure Typing for ScalaDefining 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 affine logic;– actor model and other process calculi.

Circuitries provide the most general formalism for computations, see“Multi-Level Languages are Generalized Arrows”, A. Megacz.

Page 26: Procedure Typing for Scala

Procedure Typing for ScalaDefining procedure categories

We are talking mostly about procedure typing, so we are going toconsider some special cases:

Arrow circuitries3: circuitries generalising =>[Pure] .

Executable categories: categories generalising to =>[Proc] .

Procedure categories: executable cartesian4 procedure circuitries.

3AKA plain old “arrows” in Haskell and scalaz.4i.e. having cartesian product types.

Page 27: Procedure Typing for Scala

Procedure Typing for ScalaDefining 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] withExecutable with Cartesian {

... // Some additional goodies}

Page 28: Procedure Typing for Scala

Procedure Typing for ScalaDefining procedure categories

It’s time to give a full definition 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?

Page 29: Procedure Typing for Scala

Procedure Typing for ScalaDefining procedure categories

It’s time to give a full definition 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?

Page 30: Procedure Typing for Scala

Procedure Typing for ScalaDefining procedure categories

Additionally we need a companion object for Logged[_,_] type.

That’s where circuitry-specific primitives should be defined.

object Logged {val log: Logged[Unit, Unit] = {s: String => ((),s)}

}

Page 31: Procedure Typing for Scala

Procedure Typing for ScalaDefining procedure categories

Other circuitry-specific 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 ina next major Scala release near you).

Page 32: Procedure Typing for Scala

Procedure Typing for ScalaLanguage purification by procedure typing

Note that impure code is localised to the eval method.

Thus, thorough usage of procedure typing localizesimpurities to well-controlled places in libraries.

Except for these, Scala becomes a clean multilevel language,with effective type systems inside blocks being type-and-effectsystems internal to corresponding circuitries.

Page 33: Procedure Typing for Scala

Procedure Typing for ScalaLanguage purification by procedure typing

Curry-Howard-Lambek correspondencerelates type theories, logics and categories:For cartesian closed categories:

Internal logic = constructive proposition logicInternal language = simply-typed λ-calculus

For locally cartesian closed categories:Internal logic = constructive predicate logicInternal language = dependently-typed λ-calculus...

Informally, the work of A. Megacz provides an extension of it:For arrow circuitries:

Internal logics = contextual logicsInternal languages = type-and-effect extended λ-calculi

Page 34: Procedure Typing for Scala

Procedure Typing for ScalaLanguage purification by procedure typing

Curry-Howard-Lambek correspondencerelates type theories, logics and categories:For cartesian closed categories:

Internal logic = constructive proposition logicInternal language = simply-typed λ-calculus

For locally cartesian closed categories:Internal logic = constructive predicate logicInternal language = dependently-typed λ-calculus...

Informally, the work of A. Megacz provides an extension of it:For arrow circuitries:

Internal logics = contextual logicsInternal languages = type-and-effect extended λ-calculi

Page 35: Procedure Typing for Scala

Procedure Typing for ScalaLanguage purification by procedure typing

Scala purification/modularization programme

– Design a lattice of procedure categories between Pure andProc . In particular, reimplement flow control primitives asmacro5 methods in companion objects of respective categories.

– Implement rules for lightweight effect polymorphism using asystem of implicits à la Rytz-Odersky-Haller (2012)

– Retrofit Akka with circuitries internalizing an appropriate actorcalculus + ownership/borrowing system (Haller, 2010).

5One reason for employing macros is to guarantee that scaffoldings will becompletely removed in compile time with no overhead on the bytecode level.

Page 36: Procedure Typing for Scala

Procedure Typing for ScalaLanguage purification by procedure typing

Scala purification/modularization programme

– Design a lattice of procedure categories between Pure andProc . In particular, reimplement flow control primitives asmacro5 methods in companion objects of respective categories.

– Implement rules for lightweight effect polymorphism using asystem of implicits à la Rytz-Odersky-Haller (2012)

– Retrofit Akka with circuitries internalizing an appropriate actorcalculus + ownership/borrowing system (Haller, 2010).

5One reason for employing macros is to guarantee that scaffoldings will becompletely removed in compile time with no overhead on the bytecode level.

Page 37: Procedure Typing for Scala

Procedure Typing for ScalaLanguage purification by procedure typing

Scala purification/modularization programme

– Design a lattice of procedure categories between Pure andProc . In particular, reimplement flow control primitives asmacro5 methods in companion objects of respective categories.

– Implement rules for lightweight effect polymorphism using asystem of implicits à la Rytz-Odersky-Haller (2012)

– Retrofit Akka with circuitries internalizing an appropriate actorcalculus + ownership/borrowing system (Haller, 2010).

5One reason for employing macros is to guarantee that scaffoldings will becompletely removed in compile time with no overhead on the bytecode level.

Page 38: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computation

How do circuitries compare toother notions of computation?

Page 39: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationType-and-effect systems

Type-and-effect systems are the most well studied approach toprocedure typing:

effects are specifiers as annotations for“functions”; type system is extended with rules for “effects”.

Circuitry formalism is not an alternative, but an enclosure forthem.

Page 40: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationType-and-effect systems

Type-and-effect systems are the most well studied approach toprocedure typing: effects are specifiers as annotations for“functions”; type system is extended with rules for “effects”.

Circuitry formalism is not an alternative, but an enclosure forthem.

Page 41: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationType-and-effect systems

Type-and-effect systems are the most well studied approach toprocedure typing: effects are specifiers as annotations for“functions”; type system is extended with rules for “effects”.

Circuitry formalism is not an alternative, but an enclosure forthem.

Page 42: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationType-and-effect systems

Direct implementation of type-and-effect systems– is rigid (hardly extensible) and– requires changes to the typechecker.

Embedding effects into the type system by means ofthe circuitry formalism resolves the issues above.

Page 43: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationType-and-effect systems

Direct implementation of type-and-effect systems– is rigid (hardly extensible) and– requires changes to the typechecker.

Embedding effects into the type system by means ofthe circuitry formalism resolves the issues above.

Page 44: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationMonads

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 onlydifference is that they are not equipped with non-linear composition.

Page 45: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationMonads

Monads– do not compose well,– prescribe rigid execution order,– are not general enough for concurrent computations.

Circuitries were invented to cure this.

Page 46: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationMonads

Monads– do not compose well,– prescribe rigid execution order,– are not general enough for concurrent computations.

Circuitries were invented to cure this.

Page 47: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationApplicatives

Applicatives are a special case of arrows...

If procedures of type =>[A] never depend on effects of otherprocedures 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 flexible and easy to handle: you don’t have to propagateeffects, just accumulate them behind the scenes.

Page 48: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationApplicatives

...but not a closed special case!

Composing applicatives may produce non-commutative circuitrieslike =>[Reads(file), Writes(file)] .Procedures of this type are no longer effect-independent: effect ofwrites have to be passed to subsequent reads.

Besides these, there are also inherently non-commutative arrowssuch as those arising from monads6, comonads and Hoare triples7.

6e.g. Tx = transaction monad, Cont = continuation passing monad.7pre- and postconditioned arrows.

Page 49: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationTraditional imperative approach

Can we do everything available in imperativelanguages with arrows and circuitries?

Any imperative code can be reduced to compose and affix.

The reduction process is known as variable elimination, it can beunderstood as translation to a concatenative language like Forth.

(The concatenative languages’ juxtaposition is an overloaded operator reducingto either compose or affix depending on how operands’ types match.)

But! Writing code this way can be quite cumbersome.

Page 50: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationTraditional imperative approach

Can we do everything available in imperativelanguages with arrows and circuitries?

Any imperative code can be reduced to compose and affix.

The reduction process is known as variable elimination, it can beunderstood as translation to a concatenative language like Forth.

(The concatenative languages’ juxtaposition is an overloaded operator reducingto either compose or affix depending on how operands’ types match.)

But! Writing code this way can be quite cumbersome.

Page 51: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationTraditional imperative approach

Can we do everything available in imperativelanguages with arrows and circuitries?

Any imperative code can be reduced to compose and affix.

The reduction process is known as variable elimination, it can beunderstood as translation to a concatenative language like Forth.

(The concatenative languages’ juxtaposition is an overloaded operator reducingto either compose or affix depending on how operands’ types match.)

But! Writing code this way can be quite cumbersome.

Page 52: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationTraditional imperative approach

Can we do everything available in imperativelanguages with arrows and circuitries?

Any imperative code can be reduced to compose and affix.

The reduction process is known as variable elimination, it can beunderstood as translation to a concatenative language like Forth.

(The concatenative languages’ juxtaposition is an overloaded operator reducingto either compose or affix depending on how operands’ types match.)

But! Writing code this way can be quite cumbersome.

Page 53: Procedure Typing for Scala

Procedure Typing for ScalaComparing with other notions of computationTraditional imperative approach

Can we do everything available in imperativelanguages with arrows and circuitries?

Any imperative code can be reduced to compose and affix.

The reduction process is known as variable elimination, it can beunderstood as translation to a concatenative language like Forth.

(The concatenative languages’ juxtaposition is an overloaded operator reducingto either compose or affix depending on how operands’ types match.)

But! Writing code this way can be quite cumbersome.

Page 54: Procedure Typing for Scala

Procedure Typing for ScalaDo-notation

Defining procedure categories is easyenough. How about using them?

We develop a quasi-imperative notation8 and implement it usingmacros.

Our notation shares syntax with usual Scala imperative code......but has different semantics: it compiles to a circuit ofappropriate type instead of being executed immediately.

Circuit notation for Scala is the topic of the part II...

8Akin to Haskell’s do-notation, but much easier to use.

Page 55: Procedure Typing for Scala

Procedure Typing for ScalaDo-notation

Defining procedure categories is easyenough. How about using them?

We develop a quasi-imperative notation8 and implement it usingmacros.

Our notation shares syntax with usual Scala imperative code......but has different semantics: it compiles to a circuit ofappropriate type instead of being executed immediately.

Circuit notation for Scala is the topic of the part II...

8Akin to Haskell’s do-notation, but much easier to use.

Page 56: Procedure Typing for Scala

Procedure Typing for ScalaDo-notation

Do-notation example

...but here’s a small example to keep your interest

Even pure functions have a side effect: they consume time.=>[Future] is an example of a retrofitting 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)

}

9its reify is a macro, so any procedures can be retrofitted to be =>[Future].

Page 57: Procedure Typing for Scala

Procedure Typing for ScalaDo-notation

Literature:– The marriage of effects 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

Page 58: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for Circuitires

Part II: Syntax for Circuitires

A cup of coffee?

Page 59: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresImplicit Unboxing

How do you use an arrow (say f: Logged[Int, String] ) inpresent Scala code?

println(f(5)) seems to be the obvious way, but that’simpossible, application is not defined for f.

To facilitate such natural notation, we need implicit unboxing.

Page 60: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresImplicit Unboxing

Preliminaries

A wrapping is a type F[_] equipped with eval[T](v: F[T]): Tand reify[T](expr: => T): F[T] (reify often being a macro) sothat

eval(reify(x)) ≡ x andreify(eval(x)) ≡ x for all x of the correct type.

A prototypical example where reify is a macro is Expr[T]. Examplewith no macros involved is Future[T] (with await as eval).

Page 61: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresImplicit Unboxing

Preliminaries

A wrapping is a type F[_] equipped with eval[T](v: F[T]): Tand reify[T](expr: => T): F[T] (reify often being a macro) sothat

eval(reify(x)) ≡ x andreify(eval(x)) ≡ x for all x of the correct type.

A prototypical example where reify is a macro is Expr[T]. Examplewith no macros involved is Future[T] (with await as eval).

Page 62: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresImplicit Unboxing

Preliminaries

Implicit unboxing is this: whenever a value of the wrapping typeF[T] is found where a value of type T is accepted, its eval is calledimplicitly.

In homoiconic languages (including Scala), all expressions can beconsidered initially having the type Expr[T] and being unboxed intoT by an implicit unboxing rule Expr[T] => T .

Page 63: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresImplicit Unboxing

Syntax proposal

Let’s introduce an instruction implicit[F] enabling implicitunboxing 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 defined outside of its scope.

Page 64: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresImplicit Unboxing

Code that uses futures and promises can be made much morereadable by implicit unboxing.

An example: dataflows in Akka 2.0. Presently they look like this:

flow {z << (x() + y())if (v() > u) println("z = " + z())

}

Page 65: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresImplicit Unboxing

Now this can be recast without any unintuitive empty parentheses:

flow {z << x + yif (v > u) println("z = " + z)

}

Page 66: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresImplicit 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(", ")

}

Page 67: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresPurifying Scala

Now, which procedure category should example() belong to?

As it evaluates =>[Logged], it should be =>[Logged] itself. Thisallows 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.

Page 68: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresPurifying Scala

Purity Declaration

Let’s introduce @pure annotation which explicitly forbids callingany functions with side effects and assignments of foreign variables.This renders the code pure.

Procedure with side effects have to be composed by circuitcomposition operations which are pure. The execution ofprocedures, which is impure, always lies outside of the scope.

All code examples below are to be read as @pure .

Page 69: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresPurifying Scala

Inside of @pure implicit unboxing for arrows becomesimplicit circuit notation, which is operationallyindistinguishable, but semantically different.

Page 70: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresCircuit 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).

Page 71: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresCircuit notation

Example:

=>[Logged] {f(x) + g(x)

}

Result:

(reify{x} andThen reify(dup) andThen (f affix g) andThen reify{_ + _})

Page 72: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresCircuit notation

In presence of implicit[X] every free braced block {...}which uses external symbols of the type =>[X] should betreated as =>[X] {...} , an implicit form of circuit syntax.

Page 73: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresCircuit notation

The desugaring rules producing operationally indistinguishablecircuits from imperative-style code blocks are quite complicated,but certainly doable.

To make the other direction possible, we need an additionaloperator: after .

Page 74: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresCircuit notation

Consider two arrows f: Unit => Unit and g: Unit => Unit .They can be composed in two ways: f affix g (out-of-order) andf andThen g (in-order).

affix in circuit notation will obviously look like f; g , though forandThen we need some new syntax:

=>[Future] {val n = fafter(n) g

}

Page 75: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresCircuit notation

Without after , =>[Future] and other similar circuitries respectonly dataflow ordering, but ignore the order of independent effects(e.g. writing into a log).

By combining usual imperative notation and after ,any possible circuit configurations can be achieved.

Page 76: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresCircuit 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.)

Page 77: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresCircuit notation

Blocks as ObjectsFor the sake of composability, blocks should be treated asanonymous 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 , butits anonymous descendant with a public member partialResult .

Page 78: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresCircuit 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.)

Page 79: Procedure Typing for Scala

Procedure Typing for ScalaSyntax for CircuitiresCircuit notation

The exact desugaring rules are quite complex (but perfectly real).We hope these examples gave you some insight how everythingmight work.

Thank you!