1
0
mirror of https://github.com/dhil/phd-dissertation synced 2026-04-28 15:06:29 +01:00

Compare commits

...

3 Commits

Author SHA1 Message Date
21fba05959 Other theses 2021-05-23 02:41:26 +01:00
a41fb391fc Update code 2021-05-22 20:33:28 +01:00
85757c48fd Effect handlers primer 2021-05-22 20:08:12 +01:00
7 changed files with 933 additions and 206 deletions

124
code/State.hs Normal file
View File

@@ -0,0 +1,124 @@
{- A Haskell version of the companion code for "State of effectful programming".
Tested with GHCi 8.6.5. -}
import Control.Monad.ST (runST)
import Data.STRef (newSTRef, readSTRef, writeSTRef)
-- State monad
newtype State s a = State { runState :: s -> (a,s) }
-- | State is a functor
instance Functor (State s) where
fmap f m = State (\st -> let (x, st') = runState m st in
(f x, st'))
-- | State is an applicative functor
instance Applicative (State s) where
pure x = State (\st -> (x, st))
m1 <*> m2 = State (\st -> let (f, st') = runState m1 st in
runState (fmap f m2) st')
-- | State is a monad
instance Monad (State s) where
return = pure
m >>= k = State (\st -> let (x, st') = runState m st in
runState (k x) st')
-- | State operations
get :: () -> State s s
get () = State (\st -> (st, st))
put :: s -> State s ()
put st = State (\st' -> ((), st))
-- Continuation monad
newtype Cont r a = Cont { runCont :: (a -> r) -> r }
-- | Cont is a functor
instance Functor (Cont r) where
fmap f k = Cont (\g -> runCont k (\x -> g (f x)))
-- | Cont is an applicative functor
instance Applicative (Cont r) where
pure x = Cont (\k -> k x)
k <*> k' = Cont (\r -> runCont k
(\k'' -> runCont k'
(\x -> r (k'' x))))
-- | Cont is a monad
instance Monad (Cont r) where
return = pure
m >>= k = Cont (\k' -> runCont m
(\x -> runCont (k x)
(\y -> k' y)))
-- | State operations
getk :: () -> Cont (State s a) s
getk () = Cont (\k -> State (\st -> runState (k st) st))
putk :: s -> Cont (State s a) ()
putk st' = Cont (\k -> State (\st -> runState (k ()) st'))
-- Free monad
data Free f a = Return a
| Op (f (Free f a))
-- | Free is a functor
instance Functor f => Functor (Free f) where
fmap f (Return x) = Return (f x)
fmap f (Op y) = Op (fmap (fmap f) y)
-- | Free is an applicative functor
instance Functor f => Applicative (Free f) where
pure = Return
(Return f) <*> xs = fmap f xs
(Op f) <*> xs = Op (fmap (\g -> g <*> xs) f)
-- | Free is a monad
instance Functor f => Monad (Free f) where
return = Return
(Return x) >>= k = k x
(Op y) >>= k = Op (fmap (\m' -> m' >>= k) y)
-- | Auxiliary function for constructing operation nodes
do' :: Functor f => f a -> Free f a
do' op = Op (fmap Return op)
-- Instantiate Free with state
data FreeState s r = Get (s -> r)
| Put s (() -> r)
-- | FreeState is a functor
instance Functor (FreeState s) where
fmap f (Get k) = Get (\st -> f (k st))
fmap f (Put st' k) = Put st' (\() -> f (k ()))
-- | State operations
get' :: () -> Free (FreeState s) s
get' () = do' (Get (\x -> x))
put' :: s -> Free (FreeState s) ()
put' st = do' (Put st (\() -> ()))
-- | State handler
runState' :: s -> Free (FreeState s) a -> (a, s)
runState' st0 (Op (Get k)) = runState' st0 (k st0)
runState' st0 (Op (Put st k)) = runState' st (k ())
runState' st0 (Return x) = (x, st0)
-- Generic state example
incrEven :: Monad m => (() -> m Int, Int -> m ()) -> () -> m Bool
incrEven (get, put) () = get () >>= (\st -> put (1 + st) >>= (\() -> return (even st)))
runExamples :: Int -> [(String, (Bool, Int))]
runExamples st0 = map (\(s, f) -> (s, f st0)) examples
where examples = [ ("builtin state", \st -> runST $ do
st' <- newSTRef st
v <- readSTRef st'
writeSTRef st' (v + 1)
v' <- readSTRef st'
return (even v, v'))
, ("pure state passing", \st -> (even st, st + 1))
, ("state monad", \st -> runState (incrEven (get, put) ()) st)
, ("continuation monad", \st -> runState (runCont (incrEven (getk, putk) ())
(\x -> State (\st -> (x, st)))) st)
, ("free monad", \st -> runState' st (incrEven (get', put') ())) ]

396
code/state.ml Normal file
View File

@@ -0,0 +1,396 @@
(* Companion for "State of effectful programming"
Tested with OCaml 4.10.0+multicore. *)
(* Generic direct-style incr_even *)
let even : int -> bool
= fun n -> n mod 2 = 0
let incr_even : (unit -> int) * (int -> unit) -> unit -> bool
= fun (get, put) () ->
let st = get () in
put (1 + st);
even st
(* Delimited control *)
module Prompt : sig
type 'a t
val make : unit -> 'a t
val reify : 'a t -> (('b -> 'a) -> 'a) -> 'b
val install : 'a t -> (unit -> 'a) -> 'a
end = struct
type 'a t = {
install : (unit -> 'a) -> 'a;
reify : 'b. (('b -> 'a) -> 'a) -> 'b
}
let make (type a) () =
let module M = struct
effect Prompt : (('b -> a) -> a) -> 'b
end
in
let reify f = perform (M.Prompt f) in
let install f =
match f () with
| x -> x
| effect (M.Prompt f) k -> f (continue k)
in
{ install; reify }
let install { install; _ } = install
let reify { reify; _ } = reify
let resume k v = continue k v
end
module type CTRL = sig
type ans
val reset : (unit -> ans) -> ans
val shift : (('a -> ans) -> ans) -> 'a
end
module Ctrl(R : sig type ans end) : sig
include CTRL with type ans = R.ans
end = struct
type ans = R.ans
let p : ans Prompt.t = Prompt.make ()
let reset m =
Prompt.install p m
let shift f =
Prompt.reify p
(fun k ->
Prompt.install p
(fun () ->
f (fun x ->
Prompt.install p
(fun () -> k x))))
end
module CtrlState
(S : sig type s end)
(R : sig type ans end): sig
type s = S.s
type ans = s -> R.ans * s
val get : unit -> s
val put : s -> unit
val run : (unit -> R.ans) -> ans
end = struct
type s = S.s
type ans = s -> R.ans * s
module Ctrl = Ctrl(struct type nonrec ans = ans end)
let get () = Ctrl.shift (fun k -> fun st -> k st st)
let put st' = Ctrl.shift (fun k -> fun st -> k () st')
let run m =
Ctrl.reset
(fun () ->
let x = m () in
fun st -> (x, st))
end
module CtrlIntState = CtrlState(struct type s = int end)(struct type ans = bool end)
(* Monadic programming *)
module type MONAD = sig
type 'a t
val return : 'a -> 'a t
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
end
(** State monad **)
module type STATE_MONAD = sig
type ans
type s
include MONAD
val get : unit -> s t
val put : s -> unit t
val run : (unit -> ans t) -> s -> ans * s
end
module StateMonad(S : sig type s end)(R : sig type ans end): sig
include STATE_MONAD with type s = S.s
and type ans = R.ans
end = struct
type ans = R.ans
type s = S.s
type 'a t = s -> 'a * s
let return : 'a -> 'a t
= fun x -> fun st -> (x, st)
let (>>=) : 'a t -> ('a -> 'b t) -> 'b t
= fun m k -> fun st ->
let (x, st') = m st in
k x st'
let get : unit -> s t
= fun () st -> (st, st)
let put : s -> unit t
= fun st st' -> ((), st)
let run : (unit -> ans t) -> s -> ans * s
= fun m st -> m () st
end
module IntStateMonad = StateMonad(struct type s = int end)(struct type ans = bool end)
(** Continuation monad **)
module type CONTINUATION_MONAD = sig
type r
include MONAD with type 'a t = ('a -> r) -> r
end
module ContinuationMonad(R : sig type ans end): sig
include CONTINUATION_MONAD with type r = R.ans
end = struct
type r = R.ans
type 'a t = ('a -> r) -> r
let return : 'a -> 'a t
= fun x -> fun k -> k x
let (>>=) : 'a t -> ('a -> 'b t) -> 'b t
= fun m k -> fun c ->
m (fun x -> k x c)
end
module ContinuationStateMonad
(S : sig type s end)
(R : sig type ans end): sig
type s = S.s
type ans = R.ans
include CONTINUATION_MONAD with type r = s -> ans * s
val get : unit -> s t
val put : s -> unit t
val run : (unit -> ans t) -> s -> ans * s
end = struct
type s = S.s
type ans = R.ans
module ContinuationMonad : CONTINUATION_MONAD with type r = s -> ans * s
= ContinuationMonad(struct type nonrec ans = s -> ans * s end)
include ContinuationMonad
let get : unit -> s t
= fun () -> fun k -> fun st -> k st st
let put : s -> unit t
= fun st' -> fun k -> fun st -> k () st'
let run : (unit -> R.ans t) -> s -> R.ans * s =
fun m st -> m () (fun x -> fun st -> (x, st)) st
end
module ContinuationIntStateMonad
= ContinuationStateMonad(struct type s = int end)(struct type ans = bool end)
(** Free monad **)
module type FUNCTOR = sig
type 'a t
val fmap : ('a -> 'b) -> 'a t -> 'b t
end
module type FREE_MONAD = sig
type 'a op
type 'a free = Return of 'a
| Op of 'a free op
include MONAD with type 'a t = 'a free
val do' : 'a op -> 'a free
end
module FreeMonad(F : FUNCTOR) : sig
include FREE_MONAD with type 'a op = 'a F.t
end = struct
type 'a op = 'a F.t
type 'a free = Return of 'a
| Op of 'a free F.t
type 'a t = 'a free
let return : 'a -> 'a t
= fun x -> Return x
let rec (>>=) : 'a t -> ('a -> 'b t) -> 'b t
= fun m k ->
match m with
| Return x -> k x
| Op y -> Op (F.fmap (fun m' -> m' >>= k) y)
let do' : 'a F.t -> 'a free
= fun op -> Op (F.fmap (fun x -> Return x) op)
end
module type FREE_STATE = sig
type s
type 'r opsig = Get of (s -> 'r)
| Put of s * (unit -> 'r)
include FUNCTOR with type 'r t = 'r opsig
end
module FreeState(S : sig type s end) = struct
type s = S.s
type 'r opsig = Get of (s -> 'r)
| Put of s * (unit -> 'r)
type 'r t = 'r opsig
let fmap : ('a -> 'b) -> 'a t -> 'b t
= fun f op ->
match op with
| Get k -> Get (fun st -> f (k st))
| Put (st', k) -> Put (st', fun st -> f (k ()))
end
module FreeIntStateMonad: sig
include STATE_MONAD with type s = int
and type ans = bool
end = struct
module rec FreeIntState : FREE_STATE with type s = int
= FreeState(struct type s = int end)
and FreeIntStateMonad : FREE_MONAD with type 'r op = 'r FreeIntState.opsig
= FreeMonad(FreeIntState)
open FreeIntState
include FreeIntStateMonad
type s = int
type ans = bool
let get : unit -> s t
= fun () -> do' (Get (fun st -> st))
let put : s -> unit t
= fun st -> do' (Put (st, fun () -> ()))
let rec run : (unit -> ans t) -> s -> ans * s
= fun m st ->
match m () with
| Return x -> (x, st)
| Op (Get k) -> run (fun () -> k st) st
| Op (Put (st', k)) -> run k st'
end
(** Monadic reflection **)
module Reflect
(M : MONAD)
(R : sig type ans end): sig
type ans = R.ans
val reify : (unit -> ans) -> ans M.t
val reflect : 'a M.t -> 'a
end = struct
type ans = R.ans
effect Reflect : 'a M.t -> 'a
let reify : (unit -> ans) -> ans M.t
= fun f ->
let open M in
match f () with
| x -> return x
| effect (Reflect m) k -> m >>= (continue k)
let reflect : 'a M.t -> 'a
= fun m ->
perform (Reflect m)
end
module ReflectIntStateMonad
= Reflect(IntStateMonad)(struct type ans = bool end)
module ReflectIntState = struct
open ReflectIntStateMonad
let get : unit -> int
= fun () -> reflect (IntStateMonad.get ())
let put : int -> unit
= fun st -> reflect (IntStateMonad.put st)
let run : (unit -> bool) -> int -> bool * int
= fun m st -> IntStateMonad.run (fun () -> reify m) st
end
(* Generic monadic incr_even *)
module MonadExample(T : STATE_MONAD with type s = int) = struct
let incr_even : unit -> bool T.t
= fun () ->
let open T in
(get ()) >>= (fun st -> put (1 + st)
>>= (fun () -> return (even st)))
end
(** Effect handlers **)
module type STATE_HANDLER = sig
type s
val get : unit -> s
val put : s -> unit
val run : (unit -> 'a) -> s -> 'a * s
end
module StateHandler(S : sig type s end) : STATE_HANDLER with type s = S.s = struct
type s = S.s
effect Put : s -> unit
let put st = perform (Put st)
effect Get : unit -> s
let get () = perform (Get ())
let run
= fun m st ->
let f = match m () with
| x -> (fun st -> (x, st))
| effect (Put st') k -> (fun st -> continue k () st')
| effect (Get ()) k -> (fun st -> continue k st st)
in f st
end
module IntStateHandler = StateHandler(struct type s = int end)
let run_examples () =
let examples = [
"builtin", (fun st ->
let st = ref st in let v = !st in st := 1 + v; (even v, !st));
"pure state passing", (fun st -> (even st, 1 + st));
"shift/reset", (fun st ->
CtrlIntState.run (incr_even CtrlIntState.(get, put)) st);
"state monad", (fun st ->
let module MonadStateExample = MonadExample(IntStateMonad) in
IntStateMonad.run MonadStateExample.incr_even st);
"continuation monad", (fun st ->
let module ContinuationMonadExample = MonadExample(ContinuationIntStateMonad) in
ContinuationIntStateMonad.run ContinuationMonadExample.incr_even st);
"free monad", (fun st ->
let module FreeMonadExample = MonadExample(FreeIntStateMonad) in
FreeIntStateMonad.run FreeMonadExample.incr_even st);
"monadic reflection", (fun st ->
ReflectIntState.run (incr_even ReflectIntState.(get, put)) st);
"state handler", (fun st ->
IntStateHandler.run (incr_even IntStateHandler.(get, put)) st) ]
in
List.map (fun (s, f) -> (s, f 4)) examples
(* module IntStateMRefl : MREFL with type ans := bool and type 'a t = 'a IntState.t
* = MRefl(struct type ans = bool end)(IntState)
*
* let get () = IntStateMRefl.reflect (IntState.get ())
* let put st = IntStateMRefl.reflect (IntState.put st)
* let run m st = IntState.run (IntStateMRefl.reify m) st
*
* let even : int -> bool
* = fun n -> n mod 2 = 0
*
* let incr_even : unit -> bool
* = fun () ->
* let st = get () in
* put (1 + st);
* even st *)

View File

@@ -45,8 +45,6 @@ module Queue {
fun singleton(x) { enqueue(x, empty) }
}
##
## Environment
##
@@ -106,6 +104,27 @@ fun usermgr(user, envs, m) {
## Basic IO
##
typename FileDescr = Int;
typename FileCursor = Int;
module File {
typename T = [String];
sig empty : T
var empty = [];
sig read : (FileCursor, T) ~> Option(String)
fun read(start, file) {
switch (drop(start, file)) {
case [] -> None
case x :: _ -> Some(x)
}
}
sig write : (String, FileCursor, T) ~> T
fun write(contents, fptr, file) {
take(fptr, file) ++ [contents] ++ drop(fptr, file)
}
}
sig stdout : FileDescr
var stdout = 1;
@@ -113,7 +132,7 @@ var stdout = 1;
sig puts : (FileDescr,String) {Puts:(FileDescr,String) -> () |_}-> ()
fun puts(fd, s) { do Puts(fd, s) }
sig basicIO : (Comp(a, {Puts:(FileDescr,String) -> () |e})) {Puts{_} |e}~> [String]
sig basicIO : (Comp(a, {Puts:(FileDescr,String) -> () |e})) {Puts{_} |e}~> File.T
fun basicIO(m) {
handle(m()) {
case Return(_) -> []
@@ -121,8 +140,6 @@ fun basicIO(m) {
}
}
# TODO: implement file space.
##
## Generic state handling.
##
@@ -153,118 +170,125 @@ var stderr = 2;
sig eof : String
var eof = "\x00";
sig gets : (FileDescr) {Gets:(FileDescr) -> String |_}-> String
fun gets(fd) { do Gets(fd) }
typename Mode = [|Read|Write|];
typename Mode = [|Create|Append|];
typename FileDescr = Int;
typename INode = (loc:Option(Int),refc:Int);
sig fopen : (Mode, String) {Fopen:(Mode, String) -> FileDescr |_}-> FileDescr
fun fopen(mode, filename) { do Fopen(mode, filename) }
typename INodeTable = [(INode, File.T)];
typename FileTable = [(Mode, INode)];
typename
sig fclose : (FileDescr) {Fclose:(FileDescr) -> () |_}-> ()
fun fclose(fd) { do Fclose(fd) }
# sig gets : (FileDescr) {Gets:(FileDescr) -> String |_}-> String
# fun gets(fd) { do Gets(fd) }
typename File = Queue.T(String);
# sig fopen : (Mode, String) {Fopen:(Mode, String) -> FileDescr |_}-> FileDescr
# fun fopen(mode, filename) { do Fopen(mode, filename) }
sig emptyFile : File
var emptyFile = Queue.empty;
# sig fclose : (FileDescr) {Fclose:(FileDescr) -> () |_}-> ()
# fun fclose(fd) { do Fclose(fd) }
sig writeFile : (String, File) -> File
fun writeFile(s, file) { Queue.enqueue(s, file) }
# typename File = Queue.T(String);
sig readFile : (File) ~> (String, File)
fun readFile(file) {
switch (Queue.dequeue(file)) {
case (None, file) -> (eof, file)
case (Some(s), file) -> (s, file)
}
}
# sig emptyFile : File
# var emptyFile = Queue.empty;
typename FileTable = [(FileDescr, File)];
typename FileStore = [(String, FileDescr)];
typename FileSystem = (next:Int,ft:FileTable,fs:FileStore);
# sig writeFile : (String, File) -> File
# fun writeFile(s, file) { Queue.enqueue(s, file) }
sig defaultFileSystem : () -> FileSystem
fun defaultFileSystem() {
var defaultTable = [ (stdin , emptyFile)
, (stdout, emptyFile)
, (stderr, emptyFile) ];
# sig readFile : (File) ~> (String, File)
# fun readFile(file) {
# switch (Queue.dequeue(file)) {
# case (None, file) -> (eof, file)
# case (Some(s), file) -> (s, file)
# }
# }
var defaultStore = [ ("stdin" , stdin)
, ("stdout", stdout)
, ("stderr", stderr) ];
# typename FileTable = [(FileDescr, File)];
# typename FileStore = [(String, FileDescr)];
# typename FileSystem = (next:Int,ft:FileTable,fs:FileStore);
(next=3,ft=defaultTable,fs=defaultStore)
}
# sig defaultFileSystem : () -> FileSystem
# fun defaultFileSystem() {
# var defaultTable = [ (stdin , emptyFile)
# , (stdout, emptyFile)
# , (stderr, emptyFile) ];
sig lookupFile : (FileDescr, FileSystem) ~> File
fun lookupFile(fd, fsys) {
switch (lookup(fd, fsys.ft)) {
case Nothing -> error("err: No such file(" ^^ intToString(fd) ^^ ")")
case Just(file) -> file
}
}
# var defaultStore = [ ("stdin" , stdin)
# , ("stdout", stdout)
# , ("stderr", stderr) ];
sig replaceFile : (FileDescr, File, FileSystem) ~> FileSystem
fun replaceFile(fd, file, fsys) {
var ft = modify(fd, file, fsys.ft);
(fsys with ft = ft) # TODO handle nonexistent file.
}
# (next=3,ft=defaultTable,fs=defaultStore)
# }
sig createFile : (String, FileSystem) -> (FileDescr, FileSystem)
fun createFile(filename, fsys) {
var fd = fsys.next;
(fd, (next = fd + 1, fs = (filename, fd) :: fsys.fs, ft = (fd, emptyFile) :: fsys.ft))
}
# sig lookupFile : (FileDescr, FileSystem) ~> File
# fun lookupFile(fd, fsys) {
# switch (lookup(fd, fsys.ft)) {
# case Nothing -> error("err: No such file(" ^^ intToString(fd) ^^ ")")
# case Just(file) -> file
# }
# }
sig openFile : (Mode, String, FileSystem) ~> (FileDescr, FileSystem)
fun openFile(mode, filename, fsys) {
var (fd, fsys') = switch (lookup(filename, fsys.fs)) {
case Nothing -> createFile(filename, fsys)
case Just(fd) -> (fd, fsys)
};
switch (mode) {
case Create -> error("erase")
case Append -> (fd, fsys')
}
}
# sig replaceFile : (FileDescr, File, FileSystem) ~> FileSystem
# fun replaceFile(fd, file, fsys) {
# var ft = modify(fd, file, fsys.ft);
# (fsys with ft = ft) # TODO handle nonexistent file.
# }
sig closeFile : (File) ~> File
fun closeFile((=front,=rear)) {
(front=front ++ reverse(rear), rear=[])
}
# sig createFile : (String, FileSystem) -> (FileDescr, FileSystem)
# fun createFile(filename, fsys) {
# var fd = fsys.next;
# (fd, (next = fd + 1, fs = (filename, fd) :: fsys.fs, ft = (fd, emptyFile) :: fsys.ft))
# }
sig allowState : (() {Get-,Put- |e}~> a) -> () {Get:s,Put:(s) -> () |e}~> a
fun allowState(f) { (f : (() {Get:s,Put:(s) -> () |e}~> a) <- (() {Get-,Put- |e}~> a)) }
# sig openFile : (Mode, String, FileSystem) ~> (FileDescr, FileSystem)
# fun openFile(mode, filename, fsys) {
# var (fd, fsys') = switch (lookup(filename, fsys.fs)) {
# case Nothing -> createFile(filename, fsys)
# case Just(fd) -> (fd, fsys)
# };
# switch (mode) {
# case Create -> error("erase")
# case Append -> (fd, fsys')
# }
# }
sig fileIO : (Comp(a, {Get-,Put-,Gets:(FileDescr) -> String,Puts:(FileDescr,String) -> (),Fclose:(FileDescr) -> (),Fopen:(Mode,String) -> FileDescr |e})) {Get:() {}-> FileSystem,Put:(FileSystem) -> (),Gets{_},Puts{_},Fclose{_},Fopen{_} |e}~> a
fun fileIO(m) {
handle(allowState(m)()) {
case Gets(fd, resume) ->
var fsys = get();
var (ch, file) = readFile(lookupFile(fd, fsys));
put(replaceFile(fd, file, fsys)); resume(ch)
case Puts(fd, ch, resume) ->
var fsys = get();
var fsys' = replaceFile(fd, writeFile(ch, lookupFile(fd, fsys)), fsys);
put(fsys'); resume(())
case Fopen(mode, filename, resume) ->
var fsys = get();
var (fd, fsys') = openFile(mode, filename, fsys);
put(fsys'); resume(fd)
case Fclose(fd, resume) ->
var fsys = get();
var fsys' = replaceFile(fd, closeFile(lookupFile(fd, fsys)), fsys);
put(fsys'); resume(())
}
}
# sig closeFile : (File) ~> File
# fun closeFile((=front,=rear)) {
# (front=front ++ reverse(rear), rear=[])
# }
sig redirect : (Comp(a, {Puts:(FileDescr,String) -> () |e}), FileDescr) {Puts:(FileDescr,String) -> () |e}~> a
fun redirect(m, fd) {
handle(m()) {
case Puts(_,s,resume) -> resume(puts(fd, s))
}
}
# sig allowState : (() {Get-,Put- |e}~> a) -> () {Get:s,Put:(s) -> () |e}~> a
# fun allowState(f) { (f : (() {Get:s,Put:(s) -> () |e}~> a) <- (() {Get-,Put- |e}~> a)) }
# sig fileIO : (Comp(a, {Get-,Put-,Gets:(FileDescr) -> String,Puts:(FileDescr,String) -> (),Fclose:(FileDescr) -> (),Fopen:(Mode,String) -> FileDescr |e})) {Get:() {}-> FileSystem,Put:(FileSystem) -> (),Gets{_},Puts{_},Fclose{_},Fopen{_} |e}~> a
# fun fileIO(m) {
# handle(allowState(m)()) {
# case Gets(fd, resume) ->
# var fsys = get();
# var (ch, file) = readFile(lookupFile(fd, fsys));
# put(replaceFile(fd, file, fsys)); resume(ch)
# case Puts(fd, ch, resume) ->
# var fsys = get();
# var fsys' = replaceFile(fd, writeFile(ch, lookupFile(fd, fsys)), fsys);
# put(fsys'); resume(())
# case Fopen(mode, filename, resume) ->
# var fsys = get();
# var (fd, fsys') = openFile(mode, filename, fsys);
# put(fsys'); resume(fd)
# case Fclose(fd, resume) ->
# var fsys = get();
# var fsys' = replaceFile(fd, closeFile(lookupFile(fd, fsys)), fsys);
# put(fsys'); resume(())
# }
# }
# sig redirect : (Comp(a, {Puts:(FileDescr,String) -> () |e}), FileDescr) {Puts:(FileDescr,String) -> () |e}~> a
# fun redirect(m, fd) {
# handle(m()) {
# case Puts(_,s,resume) -> resume(puts(fd, s))
# }
# }
##
## Processes
@@ -331,70 +355,70 @@ fun whoami() { getenv("USER") }
##
# Tags puts with the name of the current user.
sig provenance : (Comp(a, {Getenv:(String) -> String,Puts:(FileDescr,String) -> () |e})) {Getenv:(String) -> String,Puts:(FileDescr,String) -> () |e}~> a
fun provenance(m) {
handle(m()) {
case Puts(fd, s, resume) ->
var user = whoami();
resume(do Puts(fd, user ^^ "> " ^^ s))
}
}
# sig provenance : (Comp(a, {Getenv:(String) -> String,Puts:(FileDescr,String) -> () |e})) {Getenv:(String) -> String,Puts:(FileDescr,String) -> () |e}~> a
# fun provenance(m) {
# handle(m()) {
# case Puts(fd, s, resume) ->
# var user = whoami();
# resume(do Puts(fd, user ^^ "> " ^^ s))
# }
# }
# An example of everything plugged together: a time-shared 'Hello World'.
sig example : () {Fork:Bool,Getenv:(String) -> String,Su:(User) -> (),Puts:(FileDescr,String) -> (),Yield:() |_}~> ()
fun example() {
var pid = fork();
var () = {
if (pid) redirect(fun(){puts(stdout, "dummy")}, stderr)
else if (fork()) su(Alice)
else su(Bob)
};
var user = whoami();
puts(stdout, "Hello World!");
var uid = getenv("UID");
echo("My UID is " ^^ uid);
(if (amiroot()) { yield(); echo(user ^^ " is running as root.") } else ());
echo("My home dir is /home/" ^^ user)
}
# # An example of everything plugged together: a time-shared 'Hello World'.
# sig example : () {Fork:Bool,Getenv:(String) -> String,Su:(User) -> (),Puts:(FileDescr,String) -> (),Yield:() |_}~> ()
# fun example() {
# var pid = fork();
# var () = {
# if (pid) redirect(fun(){puts(stdout, "dummy")}, stderr)
# else if (fork()) su(Alice)
# else su(Bob)
# };
# var user = whoami();
# puts(stdout, "Hello World!");
# var uid = getenv("UID");
# echo("My UID is " ^^ uid);
# (if (amiroot()) { yield(); echo(user ^^ " is running as root.") } else ());
# echo("My home dir is /home/" ^^ user)
# }
# Wiring of handlers.
sig init : () {Fork{_},Getenv{_},Su{_},Puts{_},Yield{_} |_}~> [String]
fun init() {
basicIO(fun() {
schedule(fun() {
usermgr(Root, envs, fun() {
provenance(example)
})
})
})
}
# # Wiring of handlers.
# sig init : () {Fork{_},Getenv{_},Su{_},Puts{_},Yield{_} |_}~> [String]
# fun init() {
# basicIO(fun() {
# schedule(fun() {
# usermgr(Root, envs, fun() {
# provenance(example)
# })
# })
# })
# }
sig example' : () {Fork:Bool,Fclose:(FileDescr) -> (),Fopen:(Mode,String) -> FileDescr,Getenv:(String) -> String,Su:(User) -> (),Puts:(FileDescr,String) -> (),Yield:() |_}~> ()
fun example'() {
var pid = fork();
var () = {
if (pid) redirect(fun(){puts(stdout, "dummy")}, stderr)
else if (fork()) su(Alice)
else su(Bob)
};
var user = whoami();
var fd = fopen(Append, user ^^ ".txt");
puts(fd, "Hello World!");
var uid = getenv("UID");
echo("My UID is " ^^ uid);
(if (amiroot()) { yield(); echo(user ^^ " is running as root.") } else ());
echo("My home dir is /home/" ^^ user);
fclose(fd)
}
# sig example' : () {Fork:Bool,Fclose:(FileDescr) -> (),Fopen:(Mode,String) -> FileDescr,Getenv:(String) -> String,Su:(User) -> (),Puts:(FileDescr,String) -> (),Yield:() |_}~> ()
# fun example'() {
# var pid = fork();
# var () = {
# if (pid) redirect(fun(){puts(stdout, "dummy")}, stderr)
# else if (fork()) su(Alice)
# else su(Bob)
# };
# var user = whoami();
# var fd = fopen(Append, user ^^ ".txt");
# puts(fd, "Hello World!");
# var uid = getenv("UID");
# echo("My UID is " ^^ uid);
# (if (amiroot()) { yield(); echo(user ^^ " is running as root.") } else ());
# echo("My home dir is /home/" ^^ user);
# fclose(fd)
# }
sig init' : (FileSystem) {Fclose{_},Fopen{_},Fork{_},Get{_},Getenv{_},Gets{_},Put{_},Puts{_},Su{_},Yield{_}|_}~> ((), FileSystem)
fun init'(fsys) {
runState(fsys, fun() {
fileIO(fun() {
schedule(fun() {
usermgr(Root, envs, example')
})
})
})
}
# sig init' : (FileSystem) {Fclose{_},Fopen{_},Fork{_},Get{_},Getenv{_},Gets{_},Put{_},Puts{_},Su{_},Yield{_}|_}~> ((), FileSystem)
# fun init'(fsys) {
# runState(fsys, fun() {
# fileIO(fun() {
# schedule(fun() {
# usermgr(Root, envs, example')
# })
# })
# })
# }

View File

@@ -680,3 +680,18 @@ fun tcphandshakeFail() {
performTCP(tcpserver, 84, sfd, cfd)
}
}
#
# Grep
#
#sig grep : (String) {Await:Char,Yield:(Char) -> () |_}~> ()
fun grep(str) {
var cs = explode(str);
fun match(c,cs) {
switch (cs) {
case c' :: cs' ->
if (c == '\n') fail()
if (c == c')
}
}
}

View File

@@ -140,6 +140,8 @@
\newcommand{\Cong}{\mathrm{cong}}
\newcommand{\AlgTheory}{\ensuremath{\mathcal{T}}}
%% Handler projections.
\newcommand{\mret}{\mathrm{ret}}
\newcommand{\mops}{\mathrm{ops}}

View File

@@ -3445,4 +3445,34 @@
pages = {114--136},
publisher = {Springer},
year = {1999}
}
# Intensional type theory
@book{MartinLof84,
author = {Per Martin{-}L{\"{o}}f},
title = {Intuitionistic type theory},
series = {Studies in proof theory},
volume = {1},
publisher = {Bibliopolis},
year = {1984}
}
# Unison language
@misc{Chiusano20,
author = {Paul Chiusano and others},
title = {{Unison} language reference},
year = 2020,
note = {Revision \href{https://github.com/unisonweb/unisonweb-org/tree/cb9a1988731451311eeb372f69e9b9f576aa02d3/src/data/docs/language-reference}{cb9a198}}
}
# Effect handlers in C
@inproceedings{Leijen17b,
author = {Daan Leijen},
title = {Implementing Algebraic Effects in {C} - "Monads for Free in {C}"},
booktitle = {{APLAS}},
series = {Lecture Notes in Computer Science},
volume = {10695},
pages = {339--363},
publisher = {Springer},
year = {2017}
}

View File

@@ -458,11 +458,6 @@ I dub \emph{effect handler oriented programming}.
% not every control phenomenon is equal in terms of programmability and
% expressiveness.
% % \section{This thesis in a nutshell}
% % In this dissertation I am concerned only with
% % \citeauthor{PlotkinP09}'s deep handlers, their shallow variation, and
% % parameterised handlers which are a slight variation of deep handlers.
\section{Why first-class control matters}
From the perspective of programmers first-class control is a valuable
programming feature because it enables them to implement their own
@@ -808,7 +803,10 @@ emergence of monads as a programming abstraction began when
\citet{Moggi89,Moggi91} proposed to use monads as the mathematical
foundation for modelling computational effects in denotational
semantics. \citeauthor{Moggi91}'s view was that \emph{monads determine
computational effects}. This view was put into practice by
computational effects}. The key property of this view is that pure
values of type $A$ are distinguished from effectful computations of
type $T~A$, where $T$ is the monad representing the effect(s) of the
computation. This view was put into practice by
\citet{Wadler92,Wadler95}, who popularised monadic programming in
functional programming by demonstrating how monads increase the ease
at which programs may be retrofitted with computational effects.
@@ -869,10 +867,17 @@ different computational effects. In the following subsections we will
see three different instantiations with which we will implement global
mutable state.
Monadic programming is a top-down approach to effectful programming,
where the concrete monad structure is taken as a primitive which
controls interactions between effectful operations.
% Monadic programming is a top-down approach to effectful programming,
% where we start with a concrete framework in which we can realise
% effectful operations.
%
The monad laws ensure that monads have some algebraic structure, which
programmers can use when reasoning about their monadic programs, and
optimising compilers may take advantage of the structure to emit more
efficient code for monadic programs.
programmers can use when reasoning about their monadic
programs. Similarly, optimising compilers may take advantage of the
structure to emit more efficient code.
The success of monads as a programming idiom is difficult to
understate as monads have given rise to several popular
@@ -1285,13 +1290,13 @@ operations using the state-passing technique.
%
\[
\bl
\runState : (\UnitType \to \Free\,(\FreeState~S)\,A) \to S \to A \times S\\
\runState : (\UnitType \to \Free\,(\FreeState~S)\,R) \to S \to R \times S\\
\runState~m~st \defas
\Case\;m\,\Unit\;\{
\ba[t]{@{}l@{~}c@{~}l}
\return~x &\mapsto& (x, st);\\
\OpF\,(\Get~k) &\mapsto& \runState~st~(\lambda\Unit.k~st);\\
\OpF\,(\Put\,\Record{st';k}) &\mapsto& \runState~st'~k
\OpF\,(\Get~k) &\mapsto& \runState\,(\lambda\Unit.k~st)~st;\\
\OpF\,(\Put\,\Record{st';k}) &\mapsto& \runState~k~st'
\}\ea
\el
\]
@@ -1311,8 +1316,8 @@ that this interpretation of get and put satisfies the equations of
Definition~\ref{def:state-monad}.
%
By instantiating $S = \Int$ we can use this interpreter to run
$\incrEven$.
By instantiating $S = \Int$ and $R = \Bool$ we can use this
interpreter to run $\incrEven$.
%
\[
\runState~\incrEven~4 \reducesto^+ \Record{\True;5} : \Bool \times \Int
@@ -1323,7 +1328,7 @@ effect handlers.
\subsection{Back to direct-style}
\dhil{The problem with monads is that they do not
compose. Cite~\citet{Espinosa95}, \citet{VazouL16}}
compose. Cite~\citet{Espinosa95}, \citet{VazouL16} \citet{McBrideP08}}
%
Another problem is that monads break the basic doctrine of modular
abstraction, which we should program against an abstract interface,
@@ -1426,43 +1431,96 @@ $R = \Bool$ and $S = \Int$.
\subsubsection{Handling state}
%
At the start of the 00s decade
\citet{PlotkinP01,PlotkinP02,PlotkinP03} introduced \emph{algebraic
effects}, which is an approach to effectful programming that inverts
\citeauthor{Moggi91}'s view such that \emph{computational effects
determine monads}. In their view a computational effect is given by
a signature of effectful operations and a collection of equations that
govern their behaviour, together they generate a free monad rather
than the other way around.
\citet{PlotkinP01,PlotkinP02,PlotkinP03} introduced algebraic theories
of computational effects, or simply \emph{algebraic effects}, which
inverts \citeauthor{Moggi91}'s view of effects such that
\emph{computational effects determine monads}. In their view a
computational effect is described by an algebraic effect, which
consists of a signature of abstract operations and a collection of
equations that govern their behaviour, together they generate a free
monad rather than the other way around.
%
Algebraic effects provide a bottom-up approach to effectful
programming in which abstract effect operations are taken as
primitive. Using these operations we may build up concrete structures.
%
In practical programming terms, we may understand an algebraic effect
as an abstract interface, whose operations build the underlying free
monad.
By the end of the decade \citet{PlotkinP09,PlotkinP13} introduced
\emph{handlers for algebraic effects}, which interpret computation
trees induced by effectful operations in a similar way to runners of
free monad interpret computation trees. A crucial difference between
handlers and runners is that the handlers are based on first-class
delimited control.
%
Practical programming with algebraic effects and their handlers was
popularised by \citet{KammarLO13}, who demonstrated that algebraic
effects and handlers provide a modular basis for effectful
programming.
%\dhil{Cite \citet{PlotkinP01,PlotkinP02,PlotkinP03,PlotkinP09,PlotkinP13}.}
\begin{definition}
% A \emph{signature} $\Sigma$ is a collection of operation symbols
% $\ell : A \opto B \in \Sigma$. An operation symbol is a syntactic entity.
An algebraic effect is given by a pair
$\AlgTheory = (\Sigma,\mathsf{E})$ consisting of an effect signature
$\Sigma = \{(\ell_i : A \opto B)_i\}_i$ of typed operation symbols
$\ell_i$, whose interactions are govern by set of equations
$\mathsf{E}$.
%
We will not concern ourselves with the mathematical definition of
equation, as in this dissertation we will always fix
$\mathsf{E} = \emptyset$, meaning that the interactive patterns of
operations are unrestricted. As a consequence we will regard an
operation symbol as a syntactic entity subject only to a static
semantics. The type $A \opto B$ denotes the space of operations
whose payload has type $A$ and whose interpretation yields a value
of type $B$.
\end{definition}
%
From a programming perspective effect handlers are an operational
extension of \citet{BentonK01} style exception handlers.
As with the free monad, the meaning of an algebraic effect operation
is conferred by some separate interpreter. In the algebraic theory of
computational effects such interpreters are known as handlers for
algebraic effects, or simply \emph{effect handlers}. They were
introduced by \citet{PlotkinP09,PlotkinP13} by the end of the decade.
%
A crucial difference between effect handlers and interpreters of free
monads is that effect handlers use delimited control to realise the
behaviour of computational effects.
% The meaning of an algebraic effect is conferred by a suitable effect
% handler, or in analogy with the free monad a suitable interpreter. Effect handlers were By
% the end of the decade \citet{PlotkinP09,PlotkinP13} introduced
% \emph{handlers for algebraic effects}, which interpret computation
% trees induced by effectful operations in a similar way to runners of
% free monad interpret computation trees. A crucial difference between
% handlers and runners is that the handlers are based on first-class
% delimited control.
%
Practical programming with effect handlers was popularised by
\citet{KammarLO13}, who advocated algebraic effects and their handlers
as a modular basis for effectful programming.
Effect handlers introduce two dual control constructs.
%
\[
\ba{@{~}l@{~}r}
\Do\;\ell^{A \opto B}~M^C : B & \Handle\;M^A\;\With\;H^{A \Harrow B} : B \smallskip\\
\multicolumn{2}{c}{H^{C \Harrow D} ::= \{\Return\;x^C \mapsto M^D\} \mid \{\OpCase{\ell^{A \opto B}}{p^A}{k^{B \to D}} \mapsto M^D\} \uplus H}
\Do\;\ell^{A \opto B}~V^A : B & \Handle\;M^C\;\With\;H^{C \Harrow D} : D \smallskip\\
\multicolumn{2}{c}{H ::= \{\Return\;x^C \mapsto N^D\} \mid \{\OpCase{\ell^{A \opto B}}{p^A}{k^{B \to D}} \mapsto N^D\} \uplus H^{C \Harrow D}}
\ea
\]
The operation symbols are declared in a signature
$\ell : A \opto B \in \Sigma$.
%
The $\Do$ construct reifies the control state up to a suitable handler
and packages it up with the operation symbol $\ell$ and its payload
$V$ before transferring control to the suitable handler. As control is
transferred a hole is left in the evaluation context that must be
filled before evaluation can continue. The $\Handle$ construct
delimits $\Do$ invocations within the computation $M$ according to the
handler definition $H$. Handler definitions consist of the union of a
single $\Return$-clause and the disjoint union of zero or more
operation clauses. The $\Return$-clause specifies what to do with the
return value of a computation. An operation clause
$\OpCase{\ell}{p}{k}$ matches on an operation symbol and binds its
payload to $p$ and its continuation $k$. Note that the domain type of
the continuation agrees with the codomain type of the operation
symbol, and the codomain type of the continuation agrees with the
codomain type of the handler definition. Continuation application
fills the hole left by the $\Do$ construct, thus providing a value
interpretation of the invocation. The continuation returns inside the
handler once the $\Return$-clause computation has finished.
%
Operationally, effect handlers may be regarded as an extension of
\citet{BentonK01} style exception handlers.
We can implement mutable state with effect handlers as follows.
%
\[
\ba{@{~}l@{\qquad\quad}@{~}r}
@@ -1509,12 +1567,16 @@ interpreter is that here the continuation $k$ implicitly reinstalls
the handler, whereas in the free state monad interpreter we explicitly
reinstalled the handler via a recursive application.
%
By fixing $S = \Int$ we can use the above effect handler to run the
delimited control variant of $\incrEven$.
By fixing $S = \Int$ and $A = \Bool$ we can use the above effect
handler to run the delimited control variant of $\incrEven$.
%
\[
\runState~\incrEven~4 \reducesto^+ \Record{\True;5} : \Bool \times \Int
\]
%
Effect handlers come into their own when multiple effects are
combined. Throughout the dissertation we will see multiple examples of
handlers in action (e.g. Chapter~\ref{ch:unary-handlers}).
\subsubsection{Effect tracking}
\dhil{Cite \citet{GiffordL86}, \citet{LucassenG88}, \citet{TalpinJ92},
@@ -1554,10 +1616,70 @@ function under a handler that interprets at least $\Get$ and $\Put$.
\dhil{Mention the importance of polymorphism in effect tracking}
\section{Scope}
Since the inception of effect handlers numerous variations and
extensions of them have been proposed. In this dissertation I am
concerned only with \citeauthor{PlotkinP09}'s deep handlers, their
shallow variation, and parameterised handlers which are a slight
variation of deep handlers.
\subsection{Some pointers to elsewhere}
The literature on effect handlers is rich, and my dissertation is but
one of many on topics related to effect handlers. In this section I
provide a few pointers to related work involving effect handlers that
I will not otherwise discuss in this dissertation.
Readers interested in the mathematical theory and original development
of effect handlers should consult \citeauthor{Pretnar10}'s PhD
dissertation~\cite{Pretnar10}.
Lexical effect handlers are a variation on \citeauthor{PlotkinP09}'s
deep handlers, which provide a form of lexical scoping for effect
operations, thus statically binding them to their handlers.
%
\citeauthor{Geron19}'s PhD dissertation develops the mathematical
theory of scoped effect operations, whilst \citet{WuSH14} and
\citet{BiernackiPPS20} studies them from a programming perspective.
To get a grasp of the reasoning principles for effect handlers,
interested readers should consult \citeauthor{McLaughlin20}'s PhD
dissertation, which contains a development of relational reasoning
techniques for shallow
multi-handlers~\cite{McLaughlin20}. \citeauthor{McLaughlin20}'s
techniques draw inspiration from the logical relation reasoning
techniques for deep handlers due to \citet{BiernackiPPS18}.
\citeauthor{Ahman17}'s PhD dissertation is relevant for readers
interested in the integration of computational effects into dependent
type theories~\cite{Ahman17}. \citeauthor{Ahman17} develops an
intensional \citet{MartinLof84} style effectful dependent type theory
equipped with a novel computational dependent type.
Effect handlers were conceived in the realm of category theory to give
an algebraic treatment of exception handling~\cite{PlotkinP09}. They
were adopted early by functional programmers, who either added
language-level support for effect handlers~
\cite{Hillerstrom15,DolanWSYM15,BiernackiPPS18,Leijen17,BauerP15,BrachthauserSO20a,LindleyMM17,Chiusano20}
or embedded them in
libraries~\cite{KiselyovSS13,KiselyovI15,KiselyovS16,KammarLO13,BrachthauserS17,Brady13,XieL20}. Thus
functional perspectives on effect handlers are plentiful in the
literature. Only a few has studied effect handlers outside the realm
of functional programming: \citet{Brachthauser20} offers an
object-oriented perspective on effect handlers; \citet{Saleh19}
provides a logic programming perspective via an effect handlers
extension to Prolog; and \citet{Leijen17b} has an imperative take on
effect handlers in C.
\section{Contributions}
The key contributions of this dissertation are the following:
The key contributions of this dissertation are scattered across the
four main parts. The following listing summarises the contributions of
each part.
\paragraph{Background}
\begin{itemize}
\item A comprehensive operational characterisation of various
notions of continuations and first-class control phenomena.
\end{itemize}
\paragraph{Programming}
\begin{itemize}
\item A practical design for a programming language equipped with a
structural effect system and deep, shallow, and parameterised effect
@@ -1566,6 +1688,10 @@ The key contributions of this dissertation are the following:
demonstrating how to compose the essence of an \UNIX{}-style
operating system with user session management, task parallelism,
and file I/O using standard effects and handlers.
\end{itemize}
\paragraph{Implementation}
\begin{itemize}
\item A novel generalisation of the notion of continuation known as
\emph{generalised continuation}, which provides a succinct
foundation for implementing deep, shallow, and parameterised
@@ -1576,16 +1702,22 @@ The key contributions of this dissertation are the following:
\item An abstract machine semantics based on generalised
continuations, which characterises the low-level stack
manipulations admitted by effect handlers at runtime.
\end{itemize}
\paragraph{Expressiveness}
\begin{itemize}
\item A formal proof that deep, shallow, and parameterised handlers
are equi-expressible in the sense of typed macro-expressiveness.
\item A robust mathematical characterisation of the computational
efficiency of effect handlers, which shows that effect handlers
can improve the asymptotic runtime of certain classes of programs.
\item A comprehensive operational characterisation of various
notions of continuations and first-class control phenomena.
\end{itemize}
\section{Structure of this dissertation}
The following is a summary of the chapters belonging to each part of
this dissertation.
\paragraph{Background}
Chapter~\ref{ch:maths-prep} defines some basic mathematical
notation and constructions that are they pervasively throughout this
dissertation.
@@ -1597,6 +1729,7 @@ the various first-class sequential control operators that appear in
the literature. The application spectrum of continuations is discussed
as well as implementation strategies for first-class control.
\paragraph{Programming}
Chapter~\ref{ch:base-language} introduces a polymorphic fine-grain
call-by-value core calculus, $\BCalc$, which makes key use of
\citeauthor{Remy93}-style row polymorphism to implement polymorphic
@@ -1612,6 +1745,7 @@ oriented programming in practice by implementing a small operating
system dubbed \OSname{} based on \citeauthor{RitchieT74}'s original
\UNIX{}.
\paragraph{Implementation}
Chapter~\ref{ch:cps} develops a higher-order continuation passing
style translation for effect handlers through a series of step-wise
refinements of an initial standard continuation passing style
@@ -1627,6 +1761,7 @@ continuations into \citeauthor{FelleisenF86}'s CEK machine to obtain
an adequate abstract runtime with simultaneous support for deep,
shallow, and parameterised handlers.
\paragraph{Expressiveness}
Chapter~\ref{ch:deep-vs-shallow} shows that deep, shallow, and
parameterised notions of handlers can simulate one another up to
specific notions of administrative reduction.
@@ -1642,6 +1777,7 @@ We show that $\HPCF$ admits an asymptotically more efficient
implementation of generic count than any $\BPCF$ implementation.
%
\paragraph{Conclusions}
Chapter~\ref{ch:conclusions} concludes and discusses future work.