Skip to content

Latest commit

 

History

History
129 lines (84 loc) · 5.11 KB

File metadata and controls

129 lines (84 loc) · 5.11 KB

Programming with algebraic effects and handlers

In the last lecture we shall explore how algebraic operations and handlers can be used in programming.

Eff

There are several languages that support algebraic effects and handlers. The ones most faithful to the theory of algebraic effects are Eff and the multicore OCaml. They have very similar syntax, and we could use either, but let us use Eff, just because it was the first language with algebraic effects and handlers.

You can run Eff in your browser or install it locally. The page also has a quick overview of the syntax of Eff, which mimics the syntax of OCaml.

Reading material

We shall draw on examples from An introduction to algebraic effects and handlers and Programming with algebraic effects and handlers. Some examples can be seen also at the Effects Roset Stone.

Basic examples

Other examples, such as I/O and redirection can be seen at the try Eff page.

Multi-shot handlers

A handler has access to the continuation, and it may do with it whatever it likes. We may distinguish handlers according to how many times the continuation is invoked:

  • an exception-like handler does not invoke the continuation
  • a single-shot handler invokes the continuation exactly once
  • a multi-shot handler invokes the continuation more than once

Of course, combinations of these are possible, and there are handlers where it's difficult to "count" the number of invocations of the continuation, such as multi-threading below.

An exception-like handler is, well, like an exception handler.

A single-shot handler appears to the programmer as a form of dynamic-dispatch callbacks: performing the operation is like calling the callback, where the callback is determined dynamically by the enclosing handlers.

The most interesting (and confusing!) are multi-shot handlers. Let us have a look at one such handler.

Ambivalent choice

Ambivalent choice is a computational effect which works as follows. There is an exception Fail : unit → empty which signifies failure to compute successfully, and an operation Select : α list → α, which returns one of the elements of the list. It has to do return an element such that the subsequent computation does not fail (if possible).

With ambivalent choice, we may solve the n-queens problem (of placing n queens on an n × n chess board so they do not attack each other), see queens.eff.

Cooperative multi-threading

Operations and handlers have explicit access to continuations. A handler need not invoke a continue, it may instead store it somewhere and run another (previously stored) continuation. This way we get threads. This was worked out in thread.eff.

Tree representation of a functional

Suppose we have a functional

h : (int → bool) → bool

When we apply it to a function f : int → bool, we feel that

h f

will proceed as follows: h will ask f about the value f x₀ for some integer x₀. Depending on the result it gets, it will then ask some furter question f x₁, and so on, until it provides an answer a.

We may therefore represent such a functional h as a tree:

  • the leaves are the answers
  • a node is labeled by a question, which has two subtrees representing the two possible continuations (depending on the answer)

We may encode this as the datatype:

type tree =
  | Answer of bool
  | Question of int * tree * tree

Given such a tree, we can recreate the functional h:

let rec tree2fun t f =
  match t with
  | Answer y -> y
  | Question (x, t1, t2) -> tree2fun (if f x then t1 else t2) f

Can we go backwards? Given h, how do we get the tree? It turns out this is not possible in a purely functional setting in general (but is possible for out specific case, Google "impossible functionals"), but it is with computational effects. You can see how to do it with handlers in fun_tree.eff.

Problems

Problem: breadth-first search

Implement the breadth-first search strategy for ambivalent choice.

Problem: Monte Carlo sampling

The online Eff page has an example showing a handler which modifies a probabilistic computation (one that uses randomness) to one that computes the distribution of results. The handler computes the distribution in an exhaustive way that quickly leads to inefficiency.

Improve it by implement a Monte Carlo handler for estimating distributions of probabilistic computations.

Problem: recursive cows

Contemplate the recursive cows.