├── .gitignore ├── Makefile ├── README.md ├── code ├── State.hs ├── ehop2022.links ├── state.ml ├── unix-huawei2022.links ├── unix-msr2022.links ├── unix-nuprl2022.links ├── unix-plug2020.links ├── unix-tutorial.links ├── unix.links └── unix2.links ├── macros.tex ├── pkgs ├── mathpartir.sty └── mathwidth.sty ├── slides ├── Makefile ├── viva.bib └── viva.tex ├── thesis.bib └── thesis.tex /.gitignore: -------------------------------------------------------------------------------- 1 | ## Core latex/pdflatex auxiliary files: 2 | *.aux 3 | *.lof 4 | *.log 5 | *.lot 6 | *.fls 7 | *.out 8 | *.toc 9 | *.fmt 10 | *.fot 11 | *.cb 12 | *.cb2 13 | .*.lb 14 | 15 | ## Intermediate documents: 16 | *.dvi 17 | *.xdv 18 | *-converted-to.* 19 | # these rules might exclude image files for figures etc. 20 | # *.ps 21 | # *.eps 22 | # *.pdf 23 | 24 | ## Generated if empty string is given at "Please type another file name for output:" 25 | .pdf 26 | 27 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 28 | *.bbl 29 | *.bcf 30 | *.blg 31 | *-blx.aux 32 | *-blx.bib 33 | *.run.xml 34 | 35 | ## Build tool auxiliary files: 36 | *.fdb_latexmk 37 | *.synctex 38 | *.synctex(busy) 39 | *.synctex.gz 40 | *.synctex.gz(busy) 41 | *.pdfsync 42 | 43 | ## Auxiliary and intermediate files from other packages: 44 | # algorithms 45 | *.alg 46 | *.loa 47 | 48 | # achemso 49 | acs-*.bib 50 | 51 | # amsthm 52 | *.thm 53 | 54 | # beamer 55 | *.nav 56 | *.pre 57 | *.snm 58 | *.vrb 59 | 60 | # changes 61 | *.soc 62 | 63 | # cprotect 64 | *.cpt 65 | 66 | # elsarticle (documentclass of Elsevier journals) 67 | *.spl 68 | 69 | # endnotes 70 | *.ent 71 | 72 | # fixme 73 | *.lox 74 | 75 | # feynmf/feynmp 76 | *.mf 77 | *.mp 78 | *.t[1-9] 79 | *.t[1-9][0-9] 80 | *.tfm 81 | 82 | #(r)(e)ledmac/(r)(e)ledpar 83 | *.end 84 | *.?end 85 | *.[1-9] 86 | *.[1-9][0-9] 87 | *.[1-9][0-9][0-9] 88 | *.[1-9]R 89 | *.[1-9][0-9]R 90 | *.[1-9][0-9][0-9]R 91 | *.eledsec[1-9] 92 | *.eledsec[1-9]R 93 | *.eledsec[1-9][0-9] 94 | *.eledsec[1-9][0-9]R 95 | *.eledsec[1-9][0-9][0-9] 96 | *.eledsec[1-9][0-9][0-9]R 97 | 98 | # glossaries 99 | *.acn 100 | *.acr 101 | *.glg 102 | *.glo 103 | *.gls 104 | *.glsdefs 105 | 106 | # gnuplottex 107 | *-gnuplottex-* 108 | 109 | # gregoriotex 110 | *.gaux 111 | *.gtex 112 | 113 | # htlatex 114 | *.4ct 115 | *.4tc 116 | *.idv 117 | *.lg 118 | *.trc 119 | *.xref 120 | 121 | # hyperref 122 | *.brf 123 | 124 | # knitr 125 | *-concordance.tex 126 | # TODO Comment the next line if you want to keep your tikz graphics files 127 | *.tikz 128 | *-tikzDictionary 129 | 130 | # listings 131 | *.lol 132 | 133 | # makeidx 134 | *.idx 135 | *.ilg 136 | *.ind 137 | *.ist 138 | 139 | # minitoc 140 | *.maf 141 | *.mlf 142 | *.mlt 143 | *.mtc[0-9]* 144 | *.slf[0-9]* 145 | *.slt[0-9]* 146 | *.stc[0-9]* 147 | 148 | # minted 149 | _minted* 150 | *.pyg 151 | 152 | # morewrites 153 | *.mw 154 | 155 | # nomencl 156 | *.nlg 157 | *.nlo 158 | *.nls 159 | 160 | # pax 161 | *.pax 162 | 163 | # pdfpcnotes 164 | *.pdfpc 165 | 166 | # sagetex 167 | *.sagetex.sage 168 | *.sagetex.py 169 | *.sagetex.scmd 170 | 171 | # scrwfile 172 | *.wrt 173 | 174 | # sympy 175 | *.sout 176 | *.sympy 177 | sympy-plots-for-*.tex/ 178 | 179 | # pdfcomment 180 | *.upa 181 | *.upb 182 | 183 | # pythontex 184 | *.pytxcode 185 | pythontex-files-*/ 186 | 187 | # thmtools 188 | *.loe 189 | 190 | # TikZ & PGF 191 | *.dpth 192 | *.md5 193 | *.auxlock 194 | 195 | # todonotes 196 | *.tdo 197 | 198 | # easy-todo 199 | *.lod 200 | 201 | # xmpincl 202 | *.xmpi 203 | 204 | # xindy 205 | *.xdy 206 | 207 | # xypic precompiled matrices 208 | *.xyc 209 | 210 | # endfloat 211 | *.ttt 212 | *.fff 213 | 214 | # Latexian 215 | TSWLatexianTemp* 216 | 217 | ## Editors: 218 | # WinEdt 219 | *.bak 220 | *.sav 221 | 222 | # Texpad 223 | .texpadtmp 224 | 225 | # Kile 226 | *.backup 227 | 228 | # KBibTeX 229 | *~[0-9]* 230 | 231 | # auto folder when using emacs and auctex 232 | ./auto/* 233 | *.el 234 | 235 | # expex forward references with \gathertags 236 | *-tags.tex 237 | 238 | # standalone packages 239 | *.sta 240 | 241 | # generated if using elsarticle.cls 242 | *.spl 243 | 244 | # ignore temporaries 245 | *~ 246 | \#* 247 | 248 | # Do not add build target. 249 | thesis.pdf -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TEXC=pdflatex 2 | CFLAGS=-interaction=nonstopmode -halt-on-error -file-line-error 3 | BIBC=bibtex 4 | PAPER=thesis 5 | BIBLIO=$(PAPER) 6 | LATEST_COMMIT=$(shell git log --format="%h" -n 1) 7 | 8 | all: $(PAPER).pdf 9 | draft: $(PAPER).pdf-draft 10 | 11 | $(PAPER).aux: $(PAPER).tex 12 | $(TEXC) $(CFLAGS) $(PAPER) 13 | 14 | $(BIBLIO).bbl: $(PAPER).aux $(BIBLIO).bib 15 | $(BIBC) $(PAPER) 16 | 17 | $(PAPER).pdf: $(PAPER).aux $(BIBLIO).bbl 18 | $(TEXC) $(CFLAGS) $(PAPER) 19 | $(TEXC) $(CFLAGS) $(PAPER) 20 | 21 | $(PAPER).pdf-draft: CFLAGS:=$(CFLAGS) "\def\DRAFT{$(LATEST_COMMIT)}\input{$(PAPER)}" 22 | $(PAPER).pdf-draft: all 23 | mv $(PAPER).pdf $(PAPER)-draft.pdf 24 | tar cf thesis-draft.tar.gz $(PAPER)-draft.pdf 25 | 26 | clean: 27 | rm -f *.log *.aux *.toc *.out 28 | rm -f *.bbl *.blg *.fls *.xml 29 | rm -f *.fdb_latexmk *.vtc *.cut 30 | rm -f $(PAPER).pdf camera-ready.pdf submission.pdf 31 | rm -f *.o *.cmx *.cmo 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Foundations for programming and implementing effect handlers 2 | 3 | A copy of my dissertation can be [downloaded via my 4 | website](https://dhil.net/research/papers/thesis.pdf). 5 | 6 | ---- 7 | 8 | Submitted on May 30, 2021. Examined on August 13, 2021. 9 | 10 | The board of examiners were 11 | 12 | * [Andrew Kennedy](https://github.com/andrewjkennedy) (Facebook London) 13 | * [Edwin Brady](https://www.type-driven.org.uk/edwinb/) (University of St Andrews) 14 | * [Ohad Kammar](http://denotational.co.uk/) (The University of Edinburgh) 15 | * [Stephen Gilmore](https://homepages.inf.ed.ac.uk/stg/) (The University of Edinburgh) 16 | 17 | ## Thesis structure 18 | 19 | The dissertation is structured as follows. 20 | 21 | ### Introduction 22 | 23 | * Chapter 1 puts forth an argument for why effect handlers 24 | matter. Following this argument it provides a basic introduction to 25 | several different approaches to effectful programming through the 26 | lens of the state effect. In addition, it also declares the scope 27 | and contributions of the dissertation, and discusses some related 28 | work. 29 | 30 | ### Programming 31 | 32 | * Chapter 2 illustrates effect handler oriented programming by 33 | example by implementing a small operating system dubbed Tiny UNIX, 34 | which captures some essential traits of Ritchie and Thompson's 35 | UNIX. The implementation starts with a basic notion of file i/o, 36 | and then, it evolves into a feature-rich operating system with full 37 | file i/o, multiple user environments, multi-tasking, and more, by 38 | composing ever more effect handlers. 39 | 40 | * Chapter 3 introduces a polymorphic fine-grain call-by-value core 41 | calculus, λb, which makes key use of Rémy-style row 42 | polymorphism to implement polymorphic variants, structural records, 43 | and a structural effect system. The calculus distils the essence of 44 | the core of the Links programming language. The chapter also 45 | presents three extensions of λb, which are λh 46 | that adds deep handlers, λ that adds shallow handlers, 47 | and λ that adds parameterised handlers. 48 | 49 | ### Implementation 50 | 51 | * Chapter 4 develops a higher-order continuation passing style 52 | translation for effect handlers through a series of step-wise 53 | refinements of an initial standard continuation passing style 54 | translation for λb. Each refinement slightly modifies 55 | the notion of continuation employed by the translation. The 56 | development ultimately leads to the key invention of generalised 57 | continuation, which is used to give a continuation passing style 58 | semantics to deep, shallow, and parameterised handlers. 59 | 60 | * Chapter 5 demonstrates an application of generalised continuations 61 | to abstract machine as we plug generalised continuations into 62 | Felleisen and Friedman's CEK machine to obtain an adequate abstract 63 | runtime with simultaneous support for deep, shallow, and 64 | parameterised handlers. 65 | 66 | ### Expressiveness 67 | 68 | * Chapter 6 shows that deep, shallow, and parameterised notions of 69 | handlers can simulate one another up to specific notions of 70 | administrative reduction. 71 | 72 | * Chapter 7 studies the fundamental efficiency of effect handlers. In 73 | this chapter, we show that effect handlers enable an asymptotic 74 | improvement in runtime complexity for a certain class of 75 | functions. Specifically, we consider the *generic count* problem 76 | using a pure PCF-like base language λb (a 77 | simply typed variation of λb) and its extension with 78 | effect handlers λh. We show that 79 | λh admits an asymptotically more efficient 80 | implementation of generic count than any λb 81 | implementation. 82 | 83 | ### Conclusions 84 | * Chapter 8 concludes and discusses future work. 85 | 86 | ### Appendices 87 | 88 | * Appendix A contains a literature survey of continuations and 89 | first-class control. I classify continuations according to their 90 | operational behaviour and provide an overview of the various 91 | first-class sequential control operators that appear in the 92 | literature. The application spectrum of continuations is discussed 93 | as well as implementation strategies for first-class control. 94 | * Appendix B contains a proof that shows the `Get-get` equation for 95 | state is redundant. 96 | * Appendix C contains the proof details and gadgetry for the 97 | complexity of the effectful generic count program. 98 | * Appendix D provides a sample implementation of the Berger count 99 | program and discusses it in more detail. 100 | 101 | ## Building 102 | 103 | To build the dissertation you need the [Informatics thesis LaTeX 104 | class](https://github.com/dhil/inf-thesis-latex-cls) with the 105 | University of Edinburgh crests. Invoking `make` on the command line 106 | ought to produce a PDF copy of the dissertation named `thesis.pdf`, 107 | e.g. 108 | 109 | ```shell 110 | $ make 111 | ``` 112 | 113 | ## Timeline 114 | 115 | I submitted my thesis on May 30, 2021. It was examined on August 13, 116 | 2021, where I received pass with minor corrections. The revised thesis 117 | was submitted on December 22, 2021. It was approved on March 118 | 14, 2022. The final revision was submitted on March 23, 2022. I 119 | received my PhD award letter on March 24, 2022. My graduation 120 | ceremony took place in McEwan Hall on July 11, 2022. 121 | -------------------------------------------------------------------------------- /code/State.hs: -------------------------------------------------------------------------------- 1 | {- A Haskell version of the companion code for "State of effectful programming". 2 | Tested with GHCi 8.6.5. -} 3 | import Control.Monad.ST (runST) 4 | import Data.STRef (newSTRef, readSTRef, writeSTRef) 5 | -- State monad 6 | newtype State s a = State { runState :: s -> (a,s) } 7 | 8 | -- | State is a functor 9 | instance Functor (State s) where 10 | fmap f m = State (\st -> let (x, st') = runState m st in 11 | (f x, st')) 12 | 13 | -- | State is an applicative functor 14 | instance Applicative (State s) where 15 | pure x = State (\st -> (x, st)) 16 | m1 <*> m2 = State (\st -> let (f, st') = runState m1 st in 17 | runState (fmap f m2) st') 18 | 19 | -- | State is a monad 20 | instance Monad (State s) where 21 | return = pure 22 | m >>= k = State (\st -> let (x, st') = runState m st in 23 | runState (k x) st') 24 | 25 | -- | State operations 26 | get :: () -> State s s 27 | get () = State (\st -> (st, st)) 28 | 29 | put :: s -> State s () 30 | put st = State (\st' -> ((), st)) 31 | 32 | -- Continuation monad 33 | newtype Cont r a = Cont { runCont :: (a -> r) -> r } 34 | 35 | -- | Cont is a functor 36 | instance Functor (Cont r) where 37 | fmap f k = Cont (\g -> runCont k (\x -> g (f x))) 38 | 39 | -- | Cont is an applicative functor 40 | instance Applicative (Cont r) where 41 | pure x = Cont (\k -> k x) 42 | k <*> k' = Cont (\r -> runCont k 43 | (\k'' -> runCont k' 44 | (\x -> r (k'' x)))) 45 | 46 | -- | Cont is a monad 47 | instance Monad (Cont r) where 48 | return = pure 49 | m >>= k = Cont (\k' -> runCont m 50 | (\x -> runCont (k x) 51 | (\y -> k' y))) 52 | 53 | -- | State operations 54 | 55 | getk :: () -> Cont (State s a) s 56 | getk () = Cont (\k -> State (\st -> runState (k st) st)) 57 | 58 | putk :: s -> Cont (State s a) () 59 | putk st' = Cont (\k -> State (\st -> runState (k ()) st')) 60 | 61 | -- Free monad 62 | data Free f a = Return a 63 | | Op (f (Free f a)) 64 | 65 | -- | Free is a functor 66 | instance Functor f => Functor (Free f) where 67 | fmap f (Return x) = Return (f x) 68 | fmap f (Op y) = Op (fmap (fmap f) y) 69 | 70 | -- | Free is an applicative functor 71 | instance Functor f => Applicative (Free f) where 72 | pure = Return 73 | (Return f) <*> xs = fmap f xs 74 | (Op f) <*> xs = Op (fmap (\g -> g <*> xs) f) 75 | 76 | -- | Free is a monad 77 | instance Functor f => Monad (Free f) where 78 | return = Return 79 | (Return x) >>= k = k x 80 | (Op y) >>= k = Op (fmap (\m' -> m' >>= k) y) 81 | 82 | -- | Auxiliary function for constructing operation nodes 83 | do' :: Functor f => f a -> Free f a 84 | do' op = Op (fmap Return op) 85 | 86 | -- Instantiate Free with state 87 | data FreeState s r = Get (s -> r) 88 | | Put s (() -> r) 89 | 90 | -- | FreeState is a functor 91 | instance Functor (FreeState s) where 92 | fmap f (Get k) = Get (\st -> f (k st)) 93 | fmap f (Put st' k) = Put st' (\() -> f (k ())) 94 | 95 | -- | State operations 96 | get' :: () -> Free (FreeState s) s 97 | get' () = do' (Get (\x -> x)) 98 | 99 | put' :: s -> Free (FreeState s) () 100 | put' st = do' (Put st (\() -> ())) 101 | 102 | -- | State handler 103 | runState' :: s -> Free (FreeState s) a -> (a, s) 104 | runState' st0 (Op (Get k)) = runState' st0 (k st0) 105 | runState' st0 (Op (Put st k)) = runState' st (k ()) 106 | runState' st0 (Return x) = (x, st0) 107 | 108 | -- Generic state example 109 | incrEven :: Monad m => (() -> m Int, Int -> m ()) -> () -> m Bool 110 | incrEven (get, put) () = get () >>= (\st -> put (1 + st) >>= (\() -> return (even st))) 111 | 112 | runExamples :: Int -> [(String, (Bool, Int))] 113 | runExamples st0 = map (\(s, f) -> (s, f st0)) examples 114 | where examples = [ ("builtin state", \st -> runST $ do 115 | st' <- newSTRef st 116 | v <- readSTRef st' 117 | writeSTRef st' (v + 1) 118 | v' <- readSTRef st' 119 | return (even v, v')) 120 | , ("pure state passing", \st -> (even st, st + 1)) 121 | , ("state monad", \st -> runState (incrEven (get, put) ()) st) 122 | , ("continuation monad", \st -> runState (runCont (incrEven (getk, putk) ()) 123 | (\x -> State (\st -> (x, st)))) st) 124 | , ("free monad", \st -> runState' st (incrEven (get', put') ())) ] 125 | -------------------------------------------------------------------------------- /code/state.ml: -------------------------------------------------------------------------------- 1 | (* Companion for "State of effectful programming" 2 | Tested with OCaml 4.10.0+multicore. *) 3 | 4 | (* Generic direct-style incr_even *) 5 | let even : int -> bool 6 | = fun n -> n mod 2 = 0 7 | 8 | let incr_even : (unit -> int) * (int -> unit) -> unit -> bool 9 | = fun (get, put) () -> 10 | let st = get () in 11 | put (1 + st); 12 | even st 13 | 14 | (* Delimited control *) 15 | module Prompt : sig 16 | type 'a t 17 | val make : unit -> 'a t 18 | val reify : 'a t -> (('b -> 'a) -> 'a) -> 'b 19 | val install : 'a t -> (unit -> 'a) -> 'a 20 | end = struct 21 | type 'a t = { 22 | install : (unit -> 'a) -> 'a; 23 | reify : 'b. (('b -> 'a) -> 'a) -> 'b 24 | } 25 | 26 | let make (type a) () = 27 | let module M = struct 28 | effect Prompt : (('b -> a) -> a) -> 'b 29 | end 30 | in 31 | let reify f = perform (M.Prompt f) in 32 | let install f = 33 | match f () with 34 | | x -> x 35 | | effect (M.Prompt f) k -> f (continue k) 36 | in 37 | { install; reify } 38 | 39 | let install { install; _ } = install 40 | let reify { reify; _ } = reify 41 | let resume k v = continue k v 42 | end 43 | 44 | module type CTRL = sig 45 | type ans 46 | val reset : (unit -> ans) -> ans 47 | val shift : (('a -> ans) -> ans) -> 'a 48 | end 49 | 50 | module Ctrl(R : sig type ans end) : sig 51 | include CTRL with type ans = R.ans 52 | end = struct 53 | type ans = R.ans 54 | 55 | let p : ans Prompt.t = Prompt.make () 56 | 57 | let reset m = 58 | Prompt.install p m 59 | 60 | let shift f = 61 | Prompt.reify p 62 | (fun k -> 63 | Prompt.install p 64 | (fun () -> 65 | f (fun x -> 66 | Prompt.install p 67 | (fun () -> k x)))) 68 | end 69 | 70 | module CtrlState 71 | (S : sig type s end) 72 | (R : sig type ans end): sig 73 | type s = S.s 74 | type ans = s -> R.ans * s 75 | 76 | val get : unit -> s 77 | val put : s -> unit 78 | 79 | val run : (unit -> R.ans) -> ans 80 | end = struct 81 | type s = S.s 82 | type ans = s -> R.ans * s 83 | module Ctrl = Ctrl(struct type nonrec ans = ans end) 84 | 85 | let get () = Ctrl.shift (fun k -> fun st -> k st st) 86 | let put st' = Ctrl.shift (fun k -> fun st -> k () st') 87 | 88 | let run m = 89 | Ctrl.reset 90 | (fun () -> 91 | let x = m () in 92 | fun st -> (x, st)) 93 | end 94 | 95 | module CtrlIntState = CtrlState(struct type s = int end)(struct type ans = bool end) 96 | 97 | (* Monadic programming *) 98 | module type MONAD = sig 99 | type 'a t 100 | val return : 'a -> 'a t 101 | val (>>=) : 'a t -> ('a -> 'b t) -> 'b t 102 | end 103 | 104 | (** State monad **) 105 | module type STATE_MONAD = sig 106 | type ans 107 | type s 108 | include MONAD 109 | 110 | val get : unit -> s t 111 | val put : s -> unit t 112 | val run : (unit -> ans t) -> s -> ans * s 113 | end 114 | 115 | module StateMonad(S : sig type s end)(R : sig type ans end): sig 116 | include STATE_MONAD with type s = S.s 117 | and type ans = R.ans 118 | end = struct 119 | type ans = R.ans 120 | type s = S.s 121 | type 'a t = s -> 'a * s 122 | 123 | let return : 'a -> 'a t 124 | = fun x -> fun st -> (x, st) 125 | 126 | let (>>=) : 'a t -> ('a -> 'b t) -> 'b t 127 | = fun m k -> fun st -> 128 | let (x, st') = m st in 129 | k x st' 130 | 131 | let get : unit -> s t 132 | = fun () st -> (st, st) 133 | 134 | let put : s -> unit t 135 | = fun st st' -> ((), st) 136 | 137 | let run : (unit -> ans t) -> s -> ans * s 138 | = fun m st -> m () st 139 | end 140 | 141 | module IntStateMonad = StateMonad(struct type s = int end)(struct type ans = bool end) 142 | 143 | (** Continuation monad **) 144 | module type CONTINUATION_MONAD = sig 145 | type r 146 | include MONAD with type 'a t = ('a -> r) -> r 147 | end 148 | 149 | module ContinuationMonad(R : sig type ans end): sig 150 | include CONTINUATION_MONAD with type r = R.ans 151 | end = struct 152 | type r = R.ans 153 | type 'a t = ('a -> r) -> r 154 | 155 | let return : 'a -> 'a t 156 | = fun x -> fun k -> k x 157 | 158 | let (>>=) : 'a t -> ('a -> 'b t) -> 'b t 159 | = fun m k -> fun c -> 160 | m (fun x -> k x c) 161 | end 162 | 163 | module ContinuationStateMonad 164 | (S : sig type s end) 165 | (R : sig type ans end): sig 166 | type s = S.s 167 | type ans = R.ans 168 | include CONTINUATION_MONAD with type r = s -> ans * s 169 | 170 | val get : unit -> s t 171 | val put : s -> unit t 172 | val run : (unit -> ans t) -> s -> ans * s 173 | end = struct 174 | type s = S.s 175 | type ans = R.ans 176 | module ContinuationMonad : CONTINUATION_MONAD with type r = s -> ans * s 177 | = ContinuationMonad(struct type nonrec ans = s -> ans * s end) 178 | include ContinuationMonad 179 | 180 | let get : unit -> s t 181 | = fun () -> fun k -> fun st -> k st st 182 | 183 | let put : s -> unit t 184 | = fun st' -> fun k -> fun st -> k () st' 185 | 186 | let run : (unit -> R.ans t) -> s -> R.ans * s = 187 | fun m st -> m () (fun x -> fun st -> (x, st)) st 188 | end 189 | 190 | module ContinuationIntStateMonad 191 | = ContinuationStateMonad(struct type s = int end)(struct type ans = bool end) 192 | 193 | (** Free monad **) 194 | module type FUNCTOR = sig 195 | type 'a t 196 | val fmap : ('a -> 'b) -> 'a t -> 'b t 197 | end 198 | 199 | module type FREE_MONAD = sig 200 | type 'a op 201 | type 'a free = Return of 'a 202 | | Op of 'a free op 203 | 204 | include MONAD with type 'a t = 'a free 205 | 206 | val do' : 'a op -> 'a free 207 | end 208 | 209 | module FreeMonad(F : FUNCTOR) : sig 210 | include FREE_MONAD with type 'a op = 'a F.t 211 | end = struct 212 | type 'a op = 'a F.t 213 | type 'a free = Return of 'a 214 | | Op of 'a free F.t 215 | 216 | type 'a t = 'a free 217 | 218 | let return : 'a -> 'a t 219 | = fun x -> Return x 220 | 221 | let rec (>>=) : 'a t -> ('a -> 'b t) -> 'b t 222 | = fun m k -> 223 | match m with 224 | | Return x -> k x 225 | | Op y -> Op (F.fmap (fun m' -> m' >>= k) y) 226 | 227 | let do' : 'a F.t -> 'a free 228 | = fun op -> Op (F.fmap (fun x -> Return x) op) 229 | end 230 | 231 | module type FREE_STATE = sig 232 | type s 233 | type 'r opsig = Get of (s -> 'r) 234 | | Put of s * (unit -> 'r) 235 | include FUNCTOR with type 'r t = 'r opsig 236 | end 237 | 238 | module FreeState(S : sig type s end) = struct 239 | type s = S.s 240 | type 'r opsig = Get of (s -> 'r) 241 | | Put of s * (unit -> 'r) 242 | type 'r t = 'r opsig 243 | 244 | let fmap : ('a -> 'b) -> 'a t -> 'b t 245 | = fun f op -> 246 | match op with 247 | | Get k -> Get (fun st -> f (k st)) 248 | | Put (st', k) -> Put (st', fun st -> f (k ())) 249 | end 250 | 251 | module FreeIntStateMonad: sig 252 | include STATE_MONAD with type s = int 253 | and type ans = bool 254 | end = struct 255 | 256 | module rec FreeIntState : FREE_STATE with type s = int 257 | = FreeState(struct type s = int end) 258 | and FreeIntStateMonad : FREE_MONAD with type 'r op = 'r FreeIntState.opsig 259 | = FreeMonad(FreeIntState) 260 | 261 | open FreeIntState 262 | include FreeIntStateMonad 263 | 264 | type s = int 265 | type ans = bool 266 | 267 | let get : unit -> s t 268 | = fun () -> do' (Get (fun st -> st)) 269 | 270 | let put : s -> unit t 271 | = fun st -> do' (Put (st, fun () -> ())) 272 | 273 | let rec run : (unit -> ans t) -> s -> ans * s 274 | = fun m st -> 275 | match m () with 276 | | Return x -> (x, st) 277 | | Op (Get k) -> run (fun () -> k st) st 278 | | Op (Put (st', k)) -> run k st' 279 | end 280 | 281 | (** Monadic reflection **) 282 | module Reflect 283 | (M : MONAD) 284 | (R : sig type ans end): sig 285 | type ans = R.ans 286 | 287 | val reify : (unit -> ans) -> ans M.t 288 | val reflect : 'a M.t -> 'a 289 | 290 | end = struct 291 | type ans = R.ans 292 | effect Reflect : 'a M.t -> 'a 293 | 294 | let reify : (unit -> ans) -> ans M.t 295 | = fun f -> 296 | let open M in 297 | match f () with 298 | | x -> return x 299 | | effect (Reflect m) k -> m >>= (continue k) 300 | 301 | let reflect : 'a M.t -> 'a 302 | = fun m -> 303 | perform (Reflect m) 304 | end 305 | 306 | module ReflectIntStateMonad 307 | = Reflect(IntStateMonad)(struct type ans = bool end) 308 | 309 | module ReflectIntState = struct 310 | open ReflectIntStateMonad 311 | 312 | let get : unit -> int 313 | = fun () -> reflect (IntStateMonad.get ()) 314 | 315 | let put : int -> unit 316 | = fun st -> reflect (IntStateMonad.put st) 317 | 318 | let run : (unit -> bool) -> int -> bool * int 319 | = fun m st -> IntStateMonad.run (fun () -> reify m) st 320 | end 321 | 322 | (* Generic monadic incr_even *) 323 | module MonadExample(T : STATE_MONAD with type s = int) = struct 324 | let incr_even : unit -> bool T.t 325 | = fun () -> 326 | let open T in 327 | (get ()) >>= (fun st -> put (1 + st) 328 | >>= (fun () -> return (even st))) 329 | end 330 | 331 | (** Effect handlers **) 332 | module type STATE_HANDLER = sig 333 | type s 334 | 335 | val get : unit -> s 336 | val put : s -> unit 337 | val run : (unit -> 'a) -> s -> 'a * s 338 | end 339 | 340 | module StateHandler(S : sig type s end) : STATE_HANDLER with type s = S.s = struct 341 | type s = S.s 342 | 343 | effect Put : s -> unit 344 | let put st = perform (Put st) 345 | 346 | effect Get : unit -> s 347 | let get () = perform (Get ()) 348 | 349 | let run 350 | = fun m st -> 351 | let f = match m () with 352 | | x -> (fun st -> (x, st)) 353 | | effect (Put st') k -> (fun st -> continue k () st') 354 | | effect (Get ()) k -> (fun st -> continue k st st) 355 | in f st 356 | end 357 | 358 | module IntStateHandler = StateHandler(struct type s = int end) 359 | 360 | let run_examples () = 361 | let examples = [ 362 | "builtin", (fun st -> 363 | let st = ref st in let v = !st in st := 1 + v; (even v, !st)); 364 | "pure state passing", (fun st -> (even st, 1 + st)); 365 | "shift/reset", (fun st -> 366 | CtrlIntState.run (incr_even CtrlIntState.(get, put)) st); 367 | "state monad", (fun st -> 368 | let module MonadStateExample = MonadExample(IntStateMonad) in 369 | IntStateMonad.run MonadStateExample.incr_even st); 370 | "continuation monad", (fun st -> 371 | let module ContinuationMonadExample = MonadExample(ContinuationIntStateMonad) in 372 | ContinuationIntStateMonad.run ContinuationMonadExample.incr_even st); 373 | "free monad", (fun st -> 374 | let module FreeMonadExample = MonadExample(FreeIntStateMonad) in 375 | FreeIntStateMonad.run FreeMonadExample.incr_even st); 376 | "monadic reflection", (fun st -> 377 | ReflectIntState.run (incr_even ReflectIntState.(get, put)) st); 378 | "state handler", (fun st -> 379 | IntStateHandler.run (incr_even IntStateHandler.(get, put)) st) ] 380 | in 381 | List.map (fun (s, f) -> (s, f 4)) examples 382 | (* module IntStateMRefl : MREFL with type ans := bool and type 'a t = 'a IntState.t 383 | * = MRefl(struct type ans = bool end)(IntState) 384 | * 385 | * let get () = IntStateMRefl.reflect (IntState.get ()) 386 | * let put st = IntStateMRefl.reflect (IntState.put st) 387 | * let run m st = IntState.run (IntStateMRefl.reify m) st 388 | * 389 | * let even : int -> bool 390 | * = fun n -> n mod 2 = 0 391 | * 392 | * let incr_even : unit -> bool 393 | * = fun () -> 394 | * let st = get () in 395 | * put (1 + st); 396 | * even st *) 397 | -------------------------------------------------------------------------------- /code/unix-huawei2022.links: -------------------------------------------------------------------------------- 1 | # DISCLAIMER: THIS MODULE REQUIRES A SPECIAL BRANCH OF LINKS TO 2 | # COMPILE: https://github.com/dhil/links/tree/multi-line-comments 3 | # commit ddcc02d3 as the effect patterns are not yet available in 4 | # master (you'll find that everything is implemented twice in this 5 | # file, once with the "new" syntax, which is shadowed by an 6 | # implementation using the "old" syntax). 7 | 8 | ## Prelude 9 | typename Option(a) = [|None|Some:a|]; 10 | 11 | sig todo : (String) ~> a 12 | fun todo(s) { error("TODO: " ^^ s) } 13 | 14 | sig fail : () {Fail:Zero |_}-> a 15 | fun fail() { switch (do Fail) { } } 16 | 17 | sig lookup : (a, [(a, b)]) {Fail:Zero |_}~> b 18 | fun lookup(k, kvs) { 19 | switch (kvs) { 20 | case [] -> fail() 21 | case (k', v) :: kvs' -> 22 | if (k == k') v 23 | else lookup(k, kvs') 24 | } 25 | } 26 | 27 | sig modify : (a, b, [(a, b)]) ~> [(a, b)] 28 | fun modify(k, v, kvs) { 29 | switch (kvs) { 30 | case [] -> [] 31 | case (k', v') :: kvs' -> 32 | if (k == k') (k, v) :: kvs' 33 | else (k', v') :: modify(k, v, kvs') 34 | } 35 | } 36 | 37 | sig remove : (a, [(a, b)]) ~> [(a, b)] 38 | fun remove(k, kvs) { 39 | switch (kvs) { 40 | case [] -> [] 41 | case (k', v') :: kvs' -> 42 | if (k == k') kvs' 43 | else (k', v') :: remove(k, kvs') 44 | } 45 | } 46 | 47 | sig has : (a, [(a, b)]) ~> Bool 48 | fun has(k, kvs) { 49 | switch (kvs) { 50 | case [] -> false 51 | case (k', _) :: kvs' -> 52 | k == k' || has(k, kvs') 53 | } 54 | } 55 | 56 | #! 57 | # 58 | # Composing UNIX with Effect Handlers 59 | # An Introduction to Effect Handler Oriented Programming 60 | # Daniel Hillerström 61 | # Laboratory for Foundations of Computer Science 62 | # The University of Edinburgh, Scotland, UK 63 | # 64 | # Huawei Research Centre Zürich, Switzerland 65 | # October 13, 2022 66 | # 67 | # https://dhil.net/research/ 68 | # 69 | #? 70 | 71 | #! 72 | # 73 | # Effect handler oriented programming (EHOP) 74 | # 75 | # Key characteristics 76 | # - Extensive use of effect handlers 77 | # - High-degree of modularity 78 | # - Extremely compositional 79 | # 80 | # Some languages that support EHOP: 81 | # C/C++, Eff, Haskell, Koka, Links, Pyro, OCaml, Unison, Wasm 82 | # 83 | #? 84 | 85 | #! 86 | # 87 | # What is an effect handler? 88 | # 89 | # Operational interpretation <--- THIS TALK 90 | # Resumeable exceptions 91 | # Programmable and composable operating systems 92 | # 93 | # Software engineering interpretation 94 | # Builders for monads (monads as a design pattern) 95 | # 96 | # Functional programming interpretation 97 | # Folds over computation trees 98 | # Free interpreters 99 | # 100 | # Mathematical interpretation 101 | # Homomorphisms between free algebraic models 102 | # 103 | #? 104 | 105 | #! 106 | # 107 | # Objectives of this talk 108 | # 109 | # - Demonstrate the versatility of effect handler oriented programming 110 | # - Explain Ritchie & Thompson's (1974) UNIX as the combination of 111 | # textbook effects 112 | # + Exceptions: Process termination 113 | # + Dynamic binding: User environments 114 | # + Nondeterminism: Time-sharing 115 | # + State: File system 116 | # + ... 117 | # 118 | #? 119 | 120 | #! 121 | # 122 | # This talk at glance 123 | # 124 | # A model of UNIX with 125 | # * support for multiple users, 126 | # * time-sharing amongst processes, 127 | # * and a file system. 128 | # 129 | # Self-imposed constraints 130 | # * Interface cannot be changed 131 | # * Everything has to be definable in the calculus 132 | # 133 | # Disclaimer: We'll make some gross simplifications. A richer model of 134 | # UNIX can be found in my PhD dissertation 135 | # 136 | # (the idea of using delimited control to model operating systems is 137 | # not new, see e.g. Kiselyov and Shan (2007), Wand (1980)) 138 | # 139 | #? 140 | 141 | # 142 | # 143 | # The key idea 144 | # 145 | # *System calls* are an interface, implemented by an *operating system* 146 | # 147 | # *Effectful operations* are an interface, implemented by an *effect handler* 148 | # 149 | # 150 | 151 | # 152 | # 153 | # The key idea 154 | # 155 | # *System calls* are an interface, implemented by an *operating system* 156 | # = = 157 | # *Effectful operations* are an interface, implemented by an *effect handler* 158 | # 159 | # 160 | 161 | # 162 | # 163 | # What is an operating system? (very abstractly) 164 | # 165 | # An operating system responds to a collection of system calls 166 | # 167 | # Example tasks: 168 | # - Signalling errors 169 | # - Scheduling processes 170 | # - Reading/writing I/O 171 | # 172 | 173 | # 174 | # 175 | # What is an effect handler? (very abstractly) 176 | # 177 | # An effect handler responds a collection of abstract operation calls 178 | # 179 | # Example tasks: 180 | # - Signalling errors 181 | # - Scheduling processes 182 | # - Reading/writing I/O 183 | # 184 | # 185 | # 186 | # 187 | # 188 | # 189 | 190 | # 191 | # 192 | # What is an effect handler? (very abstractly) 193 | # 194 | # An effect handler responds a collection of abstract operation calls 195 | # 196 | # Example tasks: 197 | # - Signalling errors 198 | # - Scheduling processes 199 | # - Reading/writing I/O 200 | # 201 | # Thus an effect handler is an operating system (credit James McKinna) 202 | # (Kiselyov and Shan (2007) used delimited continuations to model 203 | # operating systems) 204 | # 205 | # 206 | 207 | 208 | # 209 | # 210 | # Objectives of this talk 211 | # 212 | # - Demonstrate the versatility of handlers 213 | # - Explain operating systems as the combination of 214 | # + Exceptions 215 | # + Dynamic binding 216 | # + Nondeterminism 217 | # + State 218 | # 219 | # 220 | 221 | # 222 | # 223 | # What is UNIX? 224 | # 225 | # UNIX is an operating system designed by Ritchie and Thompson (1974) 226 | # 227 | # Components 228 | # - Commands (system calls) 229 | # + I/O interaction, user session login, inter-process 230 | # communication, etc 231 | # - Kernel (interpreter) 232 | # + Handling of I/O, managing user sessions, scheduling of 233 | # processes 234 | # - Development environment 235 | # + Compiler tool-chains (e.g. `cc`) 236 | # - Documentation 237 | # + manual pages (e.g. `man`) 238 | # 239 | # 240 | 241 | # 242 | # 243 | # Key characteristics of UNIX (Ritchie & Thompson, 1974) 244 | # 245 | # - Support for multiple user sessions 246 | # - Time-sharing between processes 247 | # - "Everything is a file" 248 | # 249 | # 250 | 251 | #{ 252 | #! 253 | # 254 | # Basic I/O: Performing writes 255 | # 256 | typename File = String; 257 | typename FileDescr = Int; 258 | 259 | sig stdout : FileDescr 260 | var stdout = 1; 261 | 262 | sig echo : (String) {Write:(FileDescr, String) -> ()}-> () 263 | fun echo(cs) { do Write(stdout, cs) } 264 | #? 265 | #} 266 | 267 | typename File = String; 268 | typename FileDescr = Int; 269 | 270 | sig stdout : FileDescr 271 | var stdout = 1; 272 | 273 | sig echo : (String) {Write:(FileDescr, String) -> () |%}-> () 274 | fun echo(cs) { do Write(stdout, cs) } 275 | 276 | 277 | #{ 278 | #! 279 | # 280 | # Basic I/O: Handling writes 281 | # 282 | sig basicIO : ( () {Write:(FileDescr, String) -> ()}-> a ) -> (a, File) 283 | fun basicIO(m) { 284 | handle(m()) { 285 | case ans -> (ans, "") 286 | case (resume : (()) -> (a, File) )> -> 287 | var (ans, file) = resume(()); 288 | (ans, file ++ cs) 289 | } 290 | } 291 | #? 292 | #} 293 | 294 | sig basicIO : ( () {Write:(FileDescr, String) -> () |%}-> a ) { |%}-> (a, File) 295 | fun basicIO(m) { 296 | handle(m()) { 297 | case Return(ans) -> (ans, "") 298 | case Write(_, cs, resume) -> 299 | var (ans, file) = resume(()); 300 | (ans, cs ^^ file) 301 | } 302 | } 303 | 304 | #{ 305 | #! 306 | # 307 | # Basic I/O: Example 308 | # 309 | sig example0 : () -> ((), File) 310 | fun example0() { 311 | basicIO(fun() { 312 | echo("Hello"); echo("World") 313 | }) 314 | } 315 | #? 316 | #} 317 | 318 | sig example0 : () { |%}-> ((), File) 319 | fun example0() { 320 | basicIO(fun() { 321 | echo("Hello"); echo("World") 322 | }) 323 | } 324 | 325 | 326 | # 327 | # 328 | # Dynamic semantics of handlers 329 | # 330 | # (ret) handle(V) { case x -> N case ... } 331 | # ~> N[V/x] 332 | # 333 | # (op) handle(E[do Op(V)]) { case r> -> N case ... } 334 | # ~> N[V/p 335 | # ,fun(x){ handle(E[x]) { case r> -> N case ... }}/r] 336 | # (if Op \notin E) 337 | # 338 | # 339 | 340 | #{ 341 | #! 342 | # 343 | # Exceptions: Premature exits 344 | # 345 | sig exit : (Int) {Exit:(Int) -> Zero}-> a 346 | fun exit(n) { switch (do Exit(n)) { } } 347 | #? 348 | #} 349 | 350 | sig exit : (Int) {Exit:(Int) -> Zero |%}-> a 351 | fun exit(n) { switch (do Exit(n)) { } } 352 | 353 | 354 | #{ 355 | #! 356 | # 357 | # Handling exits 358 | # 359 | sig status : (() {Exit:(Int) -> Zero}-> a) -> Int 360 | fun status(m) { 361 | handle(m()) { 362 | case ans -> 0 363 | case -> n 364 | } 365 | } 366 | #? 367 | #} 368 | 369 | sig status : (() {Exit:(Int) -> Zero |%}-> a) { |%}-> Int 370 | fun status(m) { 371 | handle(m()) { 372 | case Return(_) -> 0 373 | case Exit(n, _) -> n 374 | } 375 | } 376 | 377 | #{ 378 | #! 379 | # 380 | # Handling exits: Example 381 | # 382 | sig example1 : () -> (Int, File) 383 | fun example1() { 384 | basicIO(fun() { 385 | status(fun() { 386 | echo("dead"); exit(1); echo("code") 387 | }) 388 | }) 389 | } 390 | #? 391 | #} 392 | 393 | sig example1 : () { |%}-> (Int, File) 394 | fun example1() { 395 | basicIO(fun() { 396 | status(fun() { 397 | echo("dead"); exit(1); echo("code") 398 | }) 399 | }) 400 | } 401 | 402 | #{ 403 | #! 404 | # 405 | # Does the ordering of handlers matter? 406 | # 407 | sig example1' : () -> Int 408 | fun example1'() { 409 | status(fun() { 410 | basicIO(fun() { 411 | echo("dead"); exit(1); echo("code") 412 | }) 413 | }) 414 | } 415 | #? 416 | #} 417 | 418 | sig example1' : () { |%}-> Int 419 | fun example1'() { 420 | status(fun() { 421 | basicIO(fun() { 422 | echo("dead"); exit(1); echo("code") 423 | }) 424 | }) 425 | } 426 | 427 | #{ 428 | #! 429 | # 430 | # Dynamic binding: User-specific environments (1) 431 | # 432 | typename User = [|Alice|Bob|Root|]; 433 | 434 | sig whoami : () {Ask:String}-> String 435 | fun whoami() { do Ask } 436 | #? 437 | #} 438 | 439 | typename User = [|Alice|Bob|Root|]; 440 | 441 | sig whoami : () {Ask:String |%}-> String 442 | fun whoami() { do Ask } 443 | 444 | #{ 445 | #! 446 | # 447 | # Dynamic binding: User-specific environments (2) 448 | # 449 | sig env : (User, () {Ask:String}-> a) -> a 450 | fun env(user, m) { 451 | handle(m()) { 452 | case ans -> ans 453 | case resume> -> 454 | switch (user) { 455 | case Alice -> resume("alice") 456 | case Bob -> resume("bob") 457 | case Root -> resume("root") 458 | } 459 | } 460 | } 461 | 462 | 463 | sig example2 : () -> String 464 | fun example2() { 465 | env(Root, whoami) 466 | } 467 | #? 468 | #} 469 | 470 | sig env : (User, () {Ask:String |%}-> a) { |%}-> a 471 | fun env(user, m) { 472 | handle(m()) { 473 | case Return(x) -> x 474 | case Ask(resume) -> 475 | switch (user) { 476 | case Alice -> resume("alice") 477 | case Bob -> resume("bob") 478 | case Root -> resume("root") 479 | } 480 | } 481 | } 482 | 483 | 484 | sig example2 : () { |%}-> String 485 | fun example2() { 486 | env(Root, whoami) 487 | } 488 | 489 | #! 490 | # 491 | # Aside: Dynamic binding with delimited continuations 492 | # 493 | # The idea of dynamic binding dates back to at least McCarthy (1960) 494 | # 495 | # Kiselyov, Shan, and Sabry (2006) demonstrated dynamic binding can be 496 | # simulated with delimited continuations 497 | # 498 | #? 499 | 500 | #{ 501 | #! 502 | # 503 | # User session management 504 | # 505 | sig su : (User) {Su:(User) -> ()}-> () 506 | fun su(user) { do Su(user) } 507 | 508 | sig sessionmgr : (User, () {Ask:String, Su:(User) -> ()}-> a) -> a 509 | fun sessionmgr(user, m) { 510 | env(user, fun() { 511 | handle(m()) { 512 | case ans -> ans 513 | case resume> -> 514 | env(user', fun() { resume(()) }) 515 | } 516 | }) 517 | } 518 | #? 519 | #} 520 | 521 | sig su : (User) {Su:(User) -> () |%}-> () 522 | fun su(user) { do Su(user) } 523 | 524 | sig sessionmgr : (User, () {Ask:String, Su:(User) -> () |%}-> a) { |%}-> a 525 | fun sessionmgr(user, m) { 526 | env(user, fun() { 527 | handle(m()) { 528 | case Return(ans) -> ans 529 | case Su(user', resume) -> 530 | env(user', fun() { resume(()) }) 531 | } 532 | }) 533 | } 534 | 535 | #{ 536 | #! 537 | # 538 | # Multiple user sessions example 539 | # 540 | sig example3 : () -> (Int, File) 541 | fun example3() { 542 | basicIO(fun() { 543 | sessionmgr(Root, fun() { 544 | status(fun() { 545 | su(Alice); echo(whoami()); echo(" "); 546 | su(Bob); echo(whoami()); echo(" "); 547 | su(Root); echo(whoami()) 548 | }) 549 | }) 550 | }) 551 | } 552 | #? 553 | #} 554 | 555 | sig example3 : () { |%}-> (Int, File) 556 | fun example3() { 557 | basicIO(fun() { 558 | sessionmgr(Root, fun() { 559 | status(fun() { 560 | su(Alice); echo(whoami()); echo(" "); 561 | su(Bob); echo(whoami()); echo(" "); 562 | su(Root); echo(whoami()) 563 | }) 564 | }) 565 | }) 566 | } 567 | 568 | #! 569 | # 570 | # Nondeterminism: Multi-tasking (1) 571 | # 572 | # From the man pages. 573 | # 574 | # Description 575 | # fork() creates a new process by duplicating the calling process. The 576 | # new process is referred to as the child process. The calling process 577 | # is referred to as the parent process. 578 | # 579 | # Return value 580 | # On success, the PID of the child process is returned in the parent, 581 | # and 0 is returned in the child. 582 | # 583 | #? 584 | 585 | #! 586 | # 587 | # Nondeterminism: Multi-tasking (2) 588 | # 589 | # Fork idiom 590 | # 591 | # if (fork() > 0) parent's code 592 | # else child's code 593 | # 594 | # Let's simplify fork such that it returns a boolean: true for parent, 595 | # false for child. 596 | # 597 | #? 598 | 599 | #{ 600 | #! 601 | # 602 | # Nondeterminism: Multi-tasking (3) 603 | # 604 | sig fork : () {Fork:Bool}-> Bool 605 | fun fork() { do Fork } 606 | 607 | sig nondet : (() {Fork:Bool}-> a) -> [a] 608 | fun nondet(m) { 609 | handle(m()) { 610 | case ans -> [ans] 611 | case resume> -> resume(true) ++ resume(false) 612 | } 613 | } 614 | #? 615 | #} 616 | 617 | sig fork : () {Fork:Bool |_}-> Bool 618 | fun fork() { do Fork } 619 | 620 | sig nondet : (() {Fork:Bool |%}-> a) { |%}-> [a] 621 | fun nondet(m) { 622 | handle(m()) { 623 | case Return(ans) -> [ans] 624 | case Fork(resume) -> resume(true) ++ resume(false) 625 | } 626 | } 627 | 628 | #{ 629 | #! 630 | # 631 | # Nondeterminism: Example (1) 632 | # 633 | sig ritchie : () {Write:(FileDescr, String) -> ()}-> () 634 | fun ritchie() { 635 | echo("UNIX is basically "); 636 | echo("a simple operating system, "); 637 | echo("but "); 638 | echo("you have to be a genius to understand the simplicity.\n") 639 | } 640 | 641 | sig hamlet : () {Write:(FileDescr, String) -> ()}-> () 642 | fun hamlet() { 643 | echo("To be, or not to be,\n"); 644 | echo("that is the question:\n"); 645 | echo("Whether 'tis nobler in the mind to suffer\n") 646 | } 647 | #? 648 | #} 649 | sig ritchie : () {Write:(FileDescr, String) -> () |%}-> () 650 | fun ritchie() { 651 | echo("UNIX is basically "); 652 | echo("a simple operating system, "); 653 | echo("but "); 654 | echo("you have to be a genius to understand the simplicity.\n") 655 | } 656 | 657 | sig hamlet : () {Write:(FileDescr, String) -> () |%}-> () 658 | fun hamlet() { 659 | echo("To be, or not to be,\n"); 660 | echo("that is the question:\n"); 661 | echo("Whether 'tis nobler in the mind to suffer\n") 662 | } 663 | 664 | #{ 665 | #! 666 | # 667 | # Nondeterminism: Example (2) 668 | # 669 | sig example4 : () -> ([Int], File) 670 | fun example4() { 671 | basicIO(fun() { 672 | nondet(fun() { 673 | sessionmgr(Root, fun() { 674 | status(fun() { 675 | if (fork()) { 676 | su(Alice); 677 | ritchie() 678 | } else { 679 | su(Bob); 680 | hamlet() 681 | } 682 | }) 683 | }) 684 | }) 685 | }) 686 | } 687 | #? 688 | #} 689 | 690 | sig example4 : () { |%}-> ([Int], File) 691 | fun example4() { 692 | basicIO(fun() { 693 | nondet(fun() { 694 | sessionmgr(Root, fun() { 695 | status(fun() { 696 | if (fork()) { 697 | su(Alice); 698 | ritchie() 699 | } else { 700 | su(Bob); 701 | hamlet() 702 | } 703 | }) 704 | }) 705 | }) 706 | }) 707 | } 708 | 709 | # 710 | # 711 | # Mathematically well-founded nondeterminism 712 | # 713 | # The handler `nondet` is _exactly_ the handler Plotkin and Pretnar (2013) 714 | # give for nondeterminism 715 | # It satisfies the usual (semi-lattice) equations for nondeterministic choice, i.e. 716 | # 717 | # if (fork()) M else M = M 718 | # if (fork()) M else N = if (fork()) N else M 719 | # if (fork()) L else { if (fork()) M else N } = if (fork()) { if (fork()) L else M } else N 720 | # 721 | # 722 | 723 | #{ 724 | #! 725 | # 726 | # Interrupting processes 727 | # 728 | sig interrupt : () {Interrupt:()}-> () 729 | fun interrupt() { do Interrupt } 730 | 731 | # Process reification 732 | typename Pstate(a::Type, e::Eff) 733 | = [|Done:a 734 | |Paused:() -e-> Pstate(a, e)|]; 735 | 736 | 737 | sig reifyProcess : (() {Interrupt:() |e}-> a) -e-> Pstate(a, e) 738 | fun reifyProcess(m) { 739 | handle(m()) { 740 | case ans -> Done(ans) 741 | case resume> -> Paused(fun() { resume(()) }) 742 | } 743 | } 744 | #? 745 | #} 746 | 747 | sig interrupt : () {Interrupt:() |%}-> () 748 | fun interrupt() { do Interrupt } 749 | 750 | typename Pstate(a,e::Eff) 751 | = forall q::Presence. 752 | [|Done:a 753 | |Paused:() {Interrupt{q} |e}-> Pstate(a, { |e})|]; 754 | 755 | 756 | sig reifyProcess : (() {Interrupt:() |%}-> a) { |%}-> Pstate(a, { |%}) 757 | fun reifyProcess(m) { 758 | handle(m()) { 759 | case Return(ans) -> Done(ans) 760 | case Interrupt(resume) -> Paused(fun() { resume(()) }) 761 | } 762 | } 763 | 764 | #{ 765 | #! 766 | # 767 | # Time-sharing via interrupts 768 | # 769 | sig schedule : ([Pstate(a, {Fork:Bool})]) -> [a] 770 | fun schedule(ps) { 771 | fun schedule(ps, done) { 772 | switch (ps) { 773 | case [] -> done 774 | case Done(res) :: ps' -> 775 | schedule(ps', res :: done) 776 | case Paused(resume) :: ps' -> 777 | schedule(ps' ++ nondet(resume), done) 778 | } 779 | } 780 | schedule(ps, []) 781 | } 782 | 783 | sig timeshare : (() {Fork:Bool,Interrupt:()}-> a) -> [a] 784 | fun timeshare(m) { 785 | var p = Paused(fun() { reifyProcess(m) }); 786 | schedule([p]) 787 | } 788 | #? 789 | #} 790 | 791 | sig schedule : ([Pstate(a, { Fork:Bool |%})]) { |%}~> [a] 792 | fun schedule(ps) { 793 | fun schedule(ps, done) { 794 | switch (ps) { 795 | case [] -> done 796 | case Done(res) :: ps' -> 797 | schedule(ps', res :: done) 798 | case Paused(resume) :: ps' -> 799 | schedule(ps' ++ nondet(resume), done) 800 | } 801 | } 802 | schedule(ps, []) 803 | } 804 | 805 | sig timeshare : (() {Fork:Bool,Interrupt:() |%}-> a) { |%}-> [a] 806 | fun timeshare(m) { 807 | var p = Paused(fun() { reifyProcess(m) }); 808 | schedule([p]) 809 | } 810 | 811 | #{ 812 | #! 813 | # 814 | # Injecting interrupts 815 | # 816 | # First idea: external source injects interrupts (Ahman and Pretnar (2021)) 817 | # 818 | # Second idea: bundle interrupts with other operations 819 | sig echo' : (FileDescr,String) {Interrupt:(), Write:(FileDescr,String) -> ()}-> () 820 | fun echo'(fd, cs) { interrupt(); do Write(fd, cs) } 821 | # 822 | # Third idea: overload interpretations of operations 823 | sig interruptWrite : (() {Write:(FileDescr,String) -> ()}-> a) 824 | {Interrupt:(),Write:(FileDescr,String) -> ()}-> a 825 | fun interruptWrite(m) { 826 | handle(m()) { 827 | case ans -> ans 828 | case resume> -> 829 | interrupt(); resume(do Write(fd, cs)) 830 | } 831 | } 832 | #? 833 | #} 834 | 835 | sig interruptWrite : (() {Write:(FileDescr,String) -> () |%}-> a) 836 | {Write:(FileDescr,String) -> () |%}-> a 837 | fun interruptWrite(m) { 838 | handle(m()) { 839 | case Return(res) -> res 840 | case Write(fd, cs, resume) -> 841 | interrupt(); resume(do Write(fd, cs)) 842 | } 843 | } 844 | 845 | #{ 846 | #! 847 | # 848 | # Time-sharing example 849 | # 850 | sig example5 : () -> ([Int], File) 851 | fun example5() { 852 | basicIO(fun() { 853 | timeshare(fun() { 854 | interruptWrite(fun() { 855 | sessionmgr(Root, fun() { 856 | status(fun() { 857 | if (fork()) { 858 | su(Alice); 859 | ritchie() 860 | } else { 861 | su(Bob); 862 | hamlet() 863 | } 864 | }) 865 | }) 866 | }) 867 | }) 868 | }) 869 | } 870 | #? 871 | #} 872 | sig example5 : () { |%}-> ([Int], File) 873 | fun example5() { 874 | basicIO(fun() { 875 | timeshare(fun() { 876 | interruptWrite(fun() { 877 | sessionmgr(Root, fun() { 878 | status(fun() { 879 | if (fork()) { 880 | su(Alice); 881 | ritchie() 882 | } else { 883 | su(Bob); 884 | hamlet() 885 | } 886 | }) 887 | }) 888 | }) 889 | }) 890 | }) 891 | } 892 | 893 | #{ 894 | #! 895 | # 896 | # State: File I/O 897 | # 898 | # Generic state handling 899 | sig get : () {Get:s}-> s 900 | fun get() { do Get } 901 | 902 | sig put : (s) {Put:(s) -> ()}-> () 903 | fun put(st) { do Put(st) } 904 | 905 | sig runState : (s, () {Get:() -> s,Put:(s) -> ()}-> a) -> (a, s) 906 | fun runState(st0, m) { 907 | var f = handle(m()) { 908 | case ans -> fun(st) { (ans, st) } 909 | case resume> -> fun(st) { resume(st)(st) } 910 | case resume> -> fun(_) { resume(())(st') } 911 | }; 912 | f(st0) 913 | } 914 | #? 915 | #} 916 | 917 | sig get : () {Get:s |_}-> s 918 | fun get() { do Get } 919 | 920 | sig put : (s) {Put:(s) -> () |_}-> () 921 | fun put(st) { do Put(st) } 922 | 923 | sig runState : (s, () {Get:() -> s,Put:(s) -> () |%}-> a) { |%}-> (a, s) 924 | fun runState(st0, m) { 925 | var f = handle(m()) { 926 | case Return(x) -> fun(st) { (x, st) } 927 | case Get(resume) -> fun(st) { resume(st)(st) } 928 | case Put(st',resume) -> fun(_) { resume(())(st') } 929 | }; 930 | f(st0) 931 | } 932 | 933 | #{ 934 | #! 935 | # 936 | # State: Example 937 | # 938 | sig incr : () {Get:Int,Put:(Int) -> ()}-> () 939 | fun incr() { put(get() + 1) } 940 | 941 | sig example6 : () -> ((), Int) 942 | fun example6() { 943 | runState(41, incr) 944 | } 945 | #? 946 | #} 947 | 948 | sig incr : () {Get:Int,Put:(Int) -> () |%}-> () 949 | fun incr() { put(get() + 1) } 950 | 951 | sig example6 : () { |%}-> ((), Int) 952 | fun example6() { 953 | runState(41, incr) 954 | } 955 | 956 | #! 957 | # 958 | # Basic Serial File System (BSFS) 959 | # 960 | # Directory I-List Data region 961 | # +----------------+ +-------+ +--------------------------+ 962 | # | "hamlet" |------> | 2 |---> | "To be, or not to be..." | 963 | # +----------------+ / +-------+ +--------------------------+ 964 | # | "richtie.txt" |------> | 1 |---> | "UNIX is basically..." | 965 | # +----------------+ / +-------+ +--------------------------+ 966 | # | ... | | | ... | | ... | 967 | # +----------------+ | +-------+ +--------------------------+ 968 | # | "stdout" |------> | 1 |---> | "" | 969 | # +----------------+ | +-------+ +--------------------------+ 970 | # | ... | / 971 | # +----------------+ / 972 | # | "act3" |--- 973 | # +----------------+ 974 | # 975 | # Simplifications: 976 | # - Operating directly on inode pointers 977 | # - Reads and writes will be serial 978 | # 979 | #? 980 | 981 | #{ 982 | #! 983 | # 984 | # BSFS structures 985 | # 986 | typename INode = (loc:Int,lno:Int); 987 | typename IList = [(Int, INode)]; # INode index -> INode 988 | typename Directory = [(String, Int)]; # Filename -> INode index 989 | typename DataRegion = [(Int, File)]; # Loc -> File 990 | 991 | typename FileSystem = (dir:Directory,dregion:DataRegion,inodes:IList 992 | ,lnext:Int ,inext:Int ); 993 | 994 | sig fsys0 : FileSystem 995 | var fsys0 = ( dir = [("stdout", 0)] 996 | , inodes = [(0, (loc=0, lno=1))] 997 | , dregion = [(0, "")] 998 | , lnext = 1, inext = 1 ); 999 | 1000 | 1001 | # Utility functions 1002 | sig lookup : (a, [(a, b)]) {Fail:Zero}-> b 1003 | var lookup = lookup; 1004 | 1005 | sig withDefault : (a, () {Fail:Zero}-> a) -> a 1006 | fun withDefault(d, m) { 1007 | handle(m()) { 1008 | case ans -> ans 1009 | case -> d 1010 | } 1011 | } 1012 | #? 1013 | #} 1014 | 1015 | typename INode = (loc:Int,lno:Int); 1016 | typename IList = [(Int, INode)]; # INode index -> INode 1017 | typename Directory = [(String, Int)]; # Filename -> INode index 1018 | typename DataRegion = [(Int, File)]; # Loc -> File 1019 | 1020 | typename FileSystem = (dir:Directory,dregion:DataRegion,inodes:IList 1021 | ,lnext:Int ,inext:Int ); 1022 | 1023 | sig fsys0 : FileSystem 1024 | var fsys0 = ( dir = [("stdout", 0)] 1025 | , inodes = [(0, (loc=0, lno=1))] 1026 | , dregion = [(0, "")] 1027 | , lnext = 1, inext = 1 ); 1028 | 1029 | 1030 | # Utility functions 1031 | sig lookup : (a, [(a, b)]) {Fail:Zero |%}-> b 1032 | var lookup = lookup; 1033 | 1034 | sig withDefault : (a, () {Fail:Zero |%}-> a) { |%}-> a 1035 | fun withDefault(x', m) { 1036 | handle(m()) { 1037 | case Return(x) -> x 1038 | case Fail(_) -> x' 1039 | } 1040 | } 1041 | 1042 | sig fwrite : (Int, String, FileSystem) {Fail:Zero |%}-> FileSystem 1043 | fun fwrite(ino, cs, fsys) { 1044 | var inode = lookup(ino, fsys.inodes); 1045 | var file = lookup(inode.loc, fsys.dregion); 1046 | var file' = file ^^ cs; 1047 | (fsys with dregion = modify(inode.loc, file', fsys.dregion)) 1048 | } 1049 | 1050 | sig fread : (Int, FileSystem) {Fail:Zero |%}-> String 1051 | fun fread(ino, fsys) { 1052 | var inode = lookup(ino, fsys.inodes); 1053 | lookup(inode.loc, fsys.dregion) 1054 | } 1055 | 1056 | #{ 1057 | #! 1058 | # 1059 | # Handling BSFS operations: file reading and writing 1060 | # 1061 | sig fwrite : (FileDescr, String, FileSystem) {Fail:Zero}-> FileSystem 1062 | var fwrite = fwrite; 1063 | sig fread : (FileDescr, FileSystem) {Fail:Zero}-> String 1064 | var fread = fread; 1065 | 1066 | sig fileRW : ( () { Read :(FileDescr) -> Option(String) 1067 | , Write:(FileDescr, String) -> () }-> a ) 1068 | {Get:FileSystem,Put:(FileSystem) -> ()}-> a 1069 | fun fileRW(m) { 1070 | handle(m()) { 1071 | case ans -> ans 1072 | case resume> -> 1073 | var cs = withDefault(None, fun() { 1074 | Some(fread(fd, get())) 1075 | }); resume(cs) 1076 | case resume> -> 1077 | withDefault((), fun() { 1078 | var fsys = fwrite(fd, cs, get()); 1079 | put(fsys) 1080 | }); resume(()) 1081 | } 1082 | } 1083 | #? 1084 | #} 1085 | 1086 | sig fileRW : ( () { Read :(FileDescr) -> Option(String) 1087 | , Write:(FileDescr, String) -> () |%}-> a ) 1088 | {Get:FileSystem,Put:(FileSystem) -> () |%}-> a 1089 | fun fileRW(m) { 1090 | handle(m()) { 1091 | case Return(ans) -> ans 1092 | case Read(fd, resume) -> 1093 | var cs = withDefault(None, fun() { 1094 | Some(fread(fd, get())) 1095 | }); resume(cs) 1096 | case Write(fd, cs, resume) -> 1097 | withDefault((), fun() { 1098 | var fsys = fwrite(fd, cs, get()); 1099 | put(fsys) 1100 | }); resume(()) 1101 | } 1102 | } 1103 | 1104 | sig fopen : (String, FileSystem) {Fail:Zero |%}-> FileDescr 1105 | fun fopen(fname, fsys) { lookup(fname, fsys.dir) } 1106 | 1107 | sig fcreate : (String, FileSystem) {Fail:Zero |%}-> (FileDescr, FileSystem) 1108 | fun fcreate(fname, fsys) { 1109 | if (has(fname, fsys.dir)) { 1110 | var ino = fopen(fname, fsys); 1111 | # Truncate file 1112 | var inode = lookup(ino, fsys.inodes); 1113 | var dregion = modify(inode.loc, "", fsys.dregion); 1114 | (ino, (fsys with =dregion)) 1115 | } else { 1116 | var loc = fsys.lnext; 1117 | var dregion = (loc, "") :: fsys.dregion; 1118 | 1119 | var ino = fsys.inext; 1120 | var inode = (loc=loc,lno=1); 1121 | var inodes = (ino, inode) :: fsys.inodes; 1122 | 1123 | var dir = (fname, ino) :: fsys.dir; 1124 | (ino, (=dir, =dregion, =inodes, lnext=loc+1, inext=ino+1)) 1125 | } 1126 | } 1127 | 1128 | #{ 1129 | #! 1130 | # 1131 | # BSFS operation: file opening and creation 1132 | # 1133 | sig fopen : (String, FileSystem) {Fail:Zero}-> FileDescr 1134 | var fopen = fopen; 1135 | sig fcreate : (String, FileSystem) {Fail:Zero}-> (FileDescr, FileSystem) 1136 | var fcreate = fcreate; 1137 | 1138 | sig fileOC : ( () { Open :(String) -> Option(FileDescr) 1139 | , Create:(String) -> Option(FileDescr) }-> a ) 1140 | {Get:FileSystem,Put:(FileSystem) -> () }-> a 1141 | fun fileOC(m) { 1142 | handle(m()) { 1143 | case ans -> ans 1144 | case resume> -> 1145 | var fd = withDefault(None, fun() { 1146 | Some(fopen(fname, get())) 1147 | }); resume(fd) 1148 | case resume> -> 1149 | var fd = withDefault(None, fun() { 1150 | var (fd, fsys) = fcreate(fname, get()); 1151 | put(fsys); Some(fd) 1152 | }); resume(fd) 1153 | } 1154 | } 1155 | #? 1156 | #} 1157 | 1158 | sig fileOC : ( () { Open :(String) -> Option(FileDescr) 1159 | , Create:(String) -> Option(FileDescr) |%}-> a ) 1160 | {Get:FileSystem,Put:(FileSystem) -> () |%}-> a 1161 | fun fileOC(m) { 1162 | handle(m()) { 1163 | case Return(ans) -> ans 1164 | case Open(fname, resume) -> 1165 | var fd = withDefault(None, fun() { 1166 | Some(fopen(fname, get())) 1167 | }); resume(fd) 1168 | case Create(fname, resume) -> 1169 | var fd = withDefault(None, fun() { 1170 | var (fd, fsys) = fcreate(fname, get()); 1171 | put(fsys); Some(fd) 1172 | }); resume(fd) 1173 | } 1174 | } 1175 | 1176 | #{ 1177 | #! 1178 | # 1179 | # BSFS version 0 1180 | # 1181 | sig bsfs0 : ( () { Open :(String) -> Option(FileDescr) 1182 | , Create:(String) -> Option(FileDescr) 1183 | , Read :(FileDescr) -> Option(String) 1184 | , Write:(FileDescr, String) -> () }-> a ) 1185 | {Get:FileSystem,Put:(FileSystem) -> ()}-> a 1186 | fun bsfs0(m) { 1187 | fileOC(fun() { 1188 | fileRW(m) 1189 | }) 1190 | } 1191 | #? 1192 | #} 1193 | 1194 | sig bsfs0 : ( () { Open :(String) -> Option(FileDescr) 1195 | , Create:(String) -> Option(FileDescr) 1196 | , Read :(FileDescr) -> Option(String) 1197 | , Write:(FileDescr, String) -> () |%}-> a ) 1198 | {Get:FileSystem,Put:(FileSystem) -> () |%}-> a 1199 | fun bsfs0(m) { 1200 | fileOC(fun() { 1201 | fileRW(m) 1202 | }) 1203 | } 1204 | 1205 | #{ 1206 | #! 1207 | # 1208 | # Stream redirection 1209 | # 1210 | sig > : (() -> a, String) 1211 | { Create:(String) -> Option(FileDescr) 1212 | , Exit : (Int) -> Zero 1213 | , Write :(FileDescr,String) -> () }-> a 1214 | op f > fname { 1215 | var fd = switch (do Create(fname)) { 1216 | case None -> exit(-1) 1217 | case Some(fd) -> fd 1218 | }; handle(f()) { 1219 | case ans -> ans 1220 | case resume> -> 1221 | resume(do Write(fd, cs)) 1222 | } 1223 | } 1224 | #? 1225 | #} 1226 | 1227 | sig >- : (() { |%}-> a, String) 1228 | { Create:(String) -> Option(FileDescr) 1229 | , Exit : (Int) -> Zero 1230 | , Write :(FileDescr,String) -> () |%}-> a 1231 | op f >- fname { 1232 | var fd = switch (do Create(fname)) { 1233 | case None -> exit(-1) 1234 | case Some(fd) -> fd 1235 | }; handle(f()) { 1236 | case Return(x) -> x 1237 | case Write(_, cs, resume) -> 1238 | resume(do Write(fd, cs)) 1239 | } 1240 | } 1241 | 1242 | #{ 1243 | #! 1244 | # 1245 | # Crude copy 1246 | # 1247 | sig ccp : (String, String) { Create:(String) -> Option(FileDescr) 1248 | , Exit :(Int) -> Zero 1249 | , Read :(FileDescr) -> Option(String) 1250 | , Open :(String) -> Option(FileDescr) 1251 | , Write :(FileDescr,String) -> () }-> () 1252 | fun ccp(src, dst) { 1253 | var srcfd = switch (do Open(src)) { 1254 | case None -> exit(-1) 1255 | case Some(fd) -> fd 1256 | }; 1257 | switch (do Read(srcfd)) { 1258 | case None -> exit(-1) 1259 | case Some(cs) -> fun() {echo(cs)} > dst 1260 | } 1261 | } 1262 | #? 1263 | #} 1264 | 1265 | sig ccp : (String, String) { Create:(String) -> Option(FileDescr) 1266 | , Exit :(Int) -> Zero 1267 | , Read :(FileDescr) -> Option(String) 1268 | , Open :(String) -> Option(FileDescr) 1269 | , Write :(FileDescr,String) -> () |%}-> () 1270 | fun ccp(src, dst) { 1271 | var srcfd = switch (do Open(src)) { 1272 | case None -> exit(-1) 1273 | case Some(fd) -> fd 1274 | }; 1275 | switch (do Read(srcfd)) { 1276 | case None -> exit(-1) 1277 | case Some(cs) -> fun() {echo(cs)} >- dst 1278 | } 1279 | } 1280 | 1281 | #{ 1282 | #! 1283 | # 1284 | # Plugging everything together 1285 | # 1286 | sig example7 : () -> ([Int], FileSystem) 1287 | fun example7() { 1288 | runState(fsys0, fun() { 1289 | bsfs0(fun() { 1290 | timeshare(fun() { 1291 | interruptWrite(fun() { 1292 | sessionmgr(Root, fun() { 1293 | status(fun() { 1294 | if (fork()) { 1295 | su(Alice); 1296 | ritchie > "ritchie.txt" 1297 | } else { 1298 | su(Bob); 1299 | hamlet > "hamlet"; 1300 | ccp("hamlet", "act3") 1301 | } 1302 | }) 1303 | }) 1304 | }) 1305 | }) 1306 | }) 1307 | }) 1308 | } 1309 | #? 1310 | #} 1311 | 1312 | sig example7 : () { |%}-> ([Int], FileSystem) 1313 | fun example7() { 1314 | runState(fsys0, fun() { 1315 | bsfs0(fun() { 1316 | timeshare(fun() { 1317 | interruptWrite(fun() { 1318 | sessionmgr(Root, fun() { 1319 | status(fun() { 1320 | if (fork()) { 1321 | su(Alice); 1322 | ritchie >- "ritchie.txt" 1323 | } else { 1324 | su(Bob); 1325 | hamlet >- "hamlet"; 1326 | ccp("hamlet", "act3") 1327 | } 1328 | }) 1329 | }) 1330 | }) 1331 | }) 1332 | }) 1333 | }) 1334 | } 1335 | 1336 | #! 1337 | # 1338 | # Conclusion 1339 | # 1340 | # + Effect handlers are a versatile programming abstraction 1341 | # + Possible to retrofit legacy code with new functionality 1342 | # + Operating systems can be explained in terms of handlers 1343 | # + "Every problem can be solved by adding another handler" 1344 | # 1345 | # See my PhD dissertation[1] for an implementation of UNIX fork, usage of 1346 | # shallow handlers to implement a more UNIX-y shell environment. 1347 | # 1348 | # [1] "Foundations for Programming and Implementing Effect Handlers", 1349 | # Daniel Hillerström, PhD dissertation, The University of 1350 | # Edinburgh, Scotland, UK, 2021. 1351 | # 1352 | #? 1353 | -------------------------------------------------------------------------------- /code/unix-nuprl2022.links: -------------------------------------------------------------------------------- 1 | # DISCLAIMER: THIS MODULE REQUIRES A SPECIAL BRANCH OF LINKS TO 2 | # COMPILE: https://github.com/dhil/links/tree/multi-line-comments 3 | # commit ddcc02d3 as the effect patterns are not yet available in 4 | # master (you'll find that everything is implemented twice in this 5 | # file, once with the "new" syntax, which is shadowed by an 6 | # implementation using the "old" syntax). 7 | 8 | ## Prelude 9 | typename Option(a) = [|None|Some:a|]; 10 | 11 | sig todo : (String) ~> a 12 | fun todo(s) { error("TODO: " ^^ s) } 13 | 14 | sig fail : () {Fail:Zero |_}-> a 15 | fun fail() { switch (do Fail) { } } 16 | 17 | sig lookup : (a, [(a, b)]) {Fail:Zero |_}~> b 18 | fun lookup(k, kvs) { 19 | switch (kvs) { 20 | case [] -> fail() 21 | case (k', v) :: kvs' -> 22 | if (k == k') v 23 | else lookup(k, kvs') 24 | } 25 | } 26 | 27 | sig modify : (a, b, [(a, b)]) ~> [(a, b)] 28 | fun modify(k, v, kvs) { 29 | switch (kvs) { 30 | case [] -> [] 31 | case (k', v') :: kvs' -> 32 | if (k == k') (k, v) :: kvs' 33 | else (k', v') :: modify(k, v, kvs') 34 | } 35 | } 36 | 37 | sig remove : (a, [(a, b)]) ~> [(a, b)] 38 | fun remove(k, kvs) { 39 | switch (kvs) { 40 | case [] -> [] 41 | case (k', v') :: kvs' -> 42 | if (k == k') kvs' 43 | else (k', v') :: remove(k, kvs') 44 | } 45 | } 46 | 47 | sig has : (a, [(a, b)]) ~> Bool 48 | fun has(k, kvs) { 49 | switch (kvs) { 50 | case [] -> false 51 | case (k', _) :: kvs' -> 52 | k == k' || has(k, kvs') 53 | } 54 | } 55 | 56 | #! 57 | # 58 | # Composing UNIX with Effect Handlers 59 | # An Introduction to Effect Handler Oriented Programming 60 | # Daniel Hillerström 61 | # Laboratory for Foundations of Computer Science 62 | # The University of Edinburgh, Scotland, UK 63 | # 64 | # Programming Research Lab, Northeastern University 65 | # November 2, 2022 66 | # 67 | # https://dhil.net/research/ 68 | # 69 | #? 70 | 71 | #! 72 | # 73 | # Effect handler oriented programming (EHOP) 74 | # 75 | # Key characteristics 76 | # - Extensive use of effect handlers 77 | # - High-degree of modularity 78 | # - Extremely compositional 79 | # 80 | # Some languages that support EHOP: 81 | # C/C++, Eff, Haskell, Koka, Links, Pyro, OCaml, Unison, Wasm 82 | # 83 | #? 84 | 85 | #! 86 | # 87 | # What is an effect handler? 88 | # 89 | # Operational interpretation <--- THIS TALK 90 | # Resumeable exceptions 91 | # Programmable and composable operating systems 92 | # 93 | # Software engineering interpretation 94 | # Builders for monads (monads as a design pattern) 95 | # 96 | # Functional programming interpretation 97 | # Folds over computation trees 98 | # Free interpreters 99 | # 100 | # Mathematical interpretation 101 | # Homomorphisms between free algebraic models 102 | # 103 | #? 104 | 105 | #! 106 | # 107 | # Objectives of this talk 108 | # 109 | # - Demonstrate the versatility of effect handler oriented programming 110 | # - Explain Ritchie & Thompson's (1974) UNIX as the combination of 111 | # textbook effects 112 | # + Exceptions: Process termination 113 | # + Dynamic binding: User environments 114 | # + Nondeterminism: Time-sharing 115 | # + State: File system 116 | # + ... 117 | # 118 | #? 119 | 120 | #! 121 | # 122 | # This talk at glance 123 | # 124 | # A model of UNIX with 125 | # * support for multiple users, 126 | # * time-sharing amongst processes, 127 | # * and a file system. 128 | # 129 | # Self-imposed constraints 130 | # * Interface cannot be changed 131 | # * Everything has to be definable in the calculus 132 | # 133 | # Disclaimer: We'll make some gross simplifications. A richer model of 134 | # UNIX can be found in my PhD dissertation 135 | # 136 | # (the idea of using delimited control to model operating systems is 137 | # not new, see e.g. Kiselyov and Shan (2007), Wand (1980)) 138 | # 139 | #? 140 | 141 | #! 142 | # 143 | # The key idea 144 | # 145 | # *System calls* are an interface, implemented by an *operating system* 146 | # 147 | # *Effectful operations* are an interface, implemented by an *effect handler* 148 | # 149 | #? 150 | 151 | #! 152 | # 153 | # The key idea 154 | # 155 | # *System calls* are an interface, implemented by an *operating system* 156 | # = = 157 | # *Effectful operations* are an interface, implemented by an *effect handler* 158 | # 159 | #? 160 | 161 | # 162 | # 163 | # What is an operating system? (very abstractly) 164 | # 165 | # An operating system responds to a collection of system calls 166 | # 167 | # Example tasks: 168 | # - Signalling errors 169 | # - Scheduling processes 170 | # - Reading/writing I/O 171 | # 172 | 173 | # 174 | # 175 | # What is an effect handler? (very abstractly) 176 | # 177 | # An effect handler responds a collection of abstract operation calls 178 | # 179 | # Example tasks: 180 | # - Signalling errors 181 | # - Scheduling processes 182 | # - Reading/writing I/O 183 | # 184 | # 185 | # 186 | # 187 | # 188 | # 189 | 190 | # 191 | # 192 | # What is an effect handler? (very abstractly) 193 | # 194 | # An effect handler responds a collection of abstract operation calls 195 | # 196 | # Example tasks: 197 | # - Signalling errors 198 | # - Scheduling processes 199 | # - Reading/writing I/O 200 | # 201 | # Thus an effect handler is an operating system (credit James McKinna) 202 | # (Kiselyov and Shan (2007) used delimited continuations to model 203 | # operating systems) 204 | # 205 | # 206 | 207 | 208 | # 209 | # 210 | # Objectives of this talk 211 | # 212 | # - Demonstrate the versatility of handlers 213 | # - Explain operating systems as the combination of 214 | # + Exceptions 215 | # + Dynamic binding 216 | # + Nondeterminism 217 | # + State 218 | # 219 | # 220 | 221 | # 222 | # 223 | # What is UNIX? 224 | # 225 | # UNIX is an operating system designed by Ritchie and Thompson (1974) 226 | # 227 | # Components 228 | # - Commands (system calls) 229 | # + I/O interaction, user session login, inter-process 230 | # communication, etc 231 | # - Kernel (interpreter) 232 | # + Handling of I/O, managing user sessions, scheduling of 233 | # processes 234 | # - Development environment 235 | # + Compiler tool-chains (e.g. `cc`) 236 | # - Documentation 237 | # + manual pages (e.g. `man`) 238 | # 239 | # 240 | 241 | # 242 | # 243 | # Key characteristics of UNIX (Ritchie & Thompson, 1974) 244 | # 245 | # - Support for multiple user sessions 246 | # - Time-sharing between processes 247 | # - "Everything is a file" 248 | # 249 | # 250 | 251 | #{ 252 | #! 253 | # 254 | # Basic I/O: Performing writes 255 | # 256 | typename File = String; 257 | typename FileDescr = Int; 258 | 259 | sig stdout : FileDescr 260 | var stdout = 1; 261 | 262 | sig echo : (String) {Write:(FileDescr, String) -> ()}-> () 263 | fun echo(cs) { do Write(stdout, cs) } 264 | #? 265 | #} 266 | 267 | typename File = String; 268 | typename FileDescr = Int; 269 | 270 | sig stdout : FileDescr 271 | var stdout = 1; 272 | 273 | sig echo : (String) {Write:(FileDescr, String) -> () |%}-> () 274 | fun echo(cs) { do Write(stdout, cs) } 275 | 276 | 277 | #{ 278 | #! 279 | # 280 | # Basic I/O: Handling writes 281 | # 282 | sig basicIO : ( () {Write:(FileDescr, String) -> ()}-> a ) -> (a, File) 283 | fun basicIO(m) { 284 | handle( m() ) { 285 | case ans -> (ans, "") 286 | case (resume : (()) -> (a, File))> -> 287 | var (ans, file) = resume(()); 288 | (ans, cs ++ file) 289 | } 290 | } 291 | #? 292 | #} 293 | 294 | sig basicIO : ( () {Write:(FileDescr, String) -> () |%}-> a ) { |%}-> (a, File) 295 | fun basicIO(m) { 296 | handle(m()) { 297 | case Return(ans) -> (ans, "") 298 | case Write(_, cs, resume) -> 299 | var (ans, file) = resume(()); 300 | (ans, cs ^^ file) 301 | } 302 | } 303 | 304 | #{ 305 | #! 306 | # 307 | # Basic I/O: Example 308 | # 309 | sig example0 : () -> ((), File) 310 | fun example0() { 311 | basicIO(fun() { 312 | echo("Hello"); echo("World") 313 | }) 314 | } 315 | #? 316 | #} 317 | 318 | sig example0 : () { |%}-> ((), File) 319 | fun example0() { 320 | basicIO(fun() { 321 | echo("Hello"); echo("World") 322 | }) 323 | } 324 | 325 | 326 | # 327 | # 328 | # Dynamic semantics of handlers 329 | # 330 | # (ret) handle(V) { case x -> N case ... } 331 | # ~> N[V/x] 332 | # 333 | # (op) handle(E[do Op(V)]) { case r> -> N case ... } 334 | # ~> N[V/p 335 | # ,fun(x){ handle(E[x]) { case r> -> N case ... }}/r] 336 | # (if Op \notin E) 337 | # 338 | # 339 | 340 | #{ 341 | #! 342 | # 343 | # Exceptions: Premature exits 344 | # 345 | sig exit : (Int) {Exit:(Int) -> Zero}-> a 346 | fun exit(n) { switch (do Exit(n)) { } } 347 | #? 348 | #} 349 | 350 | sig exit : (Int) {Exit:(Int) -> Zero |%}-> a 351 | fun exit(n) { switch (do Exit(n)) { } } 352 | 353 | 354 | #{ 355 | #! 356 | # 357 | # Handling exits 358 | # 359 | sig status : (() {Exit:(Int) -> Zero}-> a) -> Int 360 | fun status(m) { 361 | handle(m()) { 362 | case ans -> 0 363 | case -> n 364 | } 365 | } 366 | #? 367 | #} 368 | 369 | sig status : (() {Exit:(Int) -> Zero |%}-> a) { |%}-> Int 370 | fun status(m) { 371 | handle(m()) { 372 | case Return(_) -> 0 373 | case Exit(n, _) -> n 374 | } 375 | } 376 | 377 | #{ 378 | #! 379 | # 380 | # Handling exits: Example 381 | # 382 | sig example1 : () -> (Int, File) 383 | fun example1() { 384 | basicIO(fun() { 385 | status(fun() { 386 | echo("dead"); exit(1); echo("code") 387 | }) 388 | }) 389 | } 390 | #? 391 | #} 392 | 393 | sig example1 : () { |%}-> (Int, File) 394 | fun example1() { 395 | basicIO(fun() { 396 | status(fun() { 397 | echo("dead"); exit(1); echo("code") 398 | }) 399 | }) 400 | } 401 | 402 | #{ 403 | #! 404 | # 405 | # Does the ordering of handlers matter? 406 | # 407 | sig example1' : () -> Int 408 | fun example1'() { 409 | status(fun() { 410 | basicIO(fun() { 411 | echo("dead"); exit(1); echo("code") 412 | }) 413 | }) 414 | } 415 | #? 416 | #} 417 | 418 | sig example1' : () { |%}-> Int 419 | fun example1'() { 420 | status(fun() { 421 | basicIO(fun() { 422 | echo("dead"); exit(1); echo("code") 423 | }) 424 | }) 425 | } 426 | 427 | #{ 428 | #! 429 | # 430 | # Dynamic binding: User-specific environments (1) 431 | # 432 | typename User = [|Alice|Bob|Root|]; 433 | 434 | sig whoami : () {Ask:String}-> String 435 | fun whoami() { do Ask } 436 | #? 437 | #} 438 | 439 | typename User = [|Alice|Bob|Root|]; 440 | 441 | sig whoami : () {Ask:String |%}-> String 442 | fun whoami() { do Ask } 443 | 444 | #{ 445 | #! 446 | # 447 | # Dynamic binding: User-specific environments (2) 448 | # 449 | sig env : (User, () {Ask:String}-> a) -> a 450 | fun env(user, m) { 451 | handle(m()) { 452 | case ans -> ans 453 | case resume> -> 454 | switch (user) { 455 | case Alice -> resume("alice") 456 | case Bob -> resume("bob") 457 | case Root -> resume("root") 458 | } 459 | } 460 | } 461 | 462 | 463 | sig example2 : () -> String 464 | fun example2() { 465 | env(Root, whoami) 466 | } 467 | #? 468 | #} 469 | 470 | sig env : (User, () {Ask:String |%}-> a) { |%}-> a 471 | fun env(user, m) { 472 | handle(m()) { 473 | case Return(x) -> x 474 | case Ask(resume) -> 475 | switch (user) { 476 | case Alice -> resume("alice") 477 | case Bob -> resume("bob") 478 | case Root -> resume("root") 479 | } 480 | } 481 | } 482 | 483 | 484 | sig example2 : () { |%}-> String 485 | fun example2() { 486 | env(Root, whoami) 487 | } 488 | 489 | #! 490 | # 491 | # Aside: Dynamic binding with delimited continuations 492 | # 493 | # The idea of dynamic binding dates back to at least McCarthy (1960) 494 | # 495 | # Kiselyov, Shan, and Sabry (2006) demonstrated dynamic binding can be 496 | # simulated with delimited continuations 497 | # 498 | #? 499 | 500 | #{ 501 | #! 502 | # 503 | # User session management 504 | # 505 | sig su : (User) {Su:(User) -> ()}-> () 506 | fun su(user) { do Su(user) } 507 | 508 | sig sessionmgr : (User, () {Ask:String, Su:(User) -> ()}-> a) -> a 509 | fun sessionmgr(user, m) { 510 | env(user, fun() { 511 | handle(m()) { 512 | case ans -> ans 513 | case resume> -> 514 | env(user', fun() { resume(()) }) 515 | } 516 | }) 517 | } 518 | #? 519 | #} 520 | 521 | sig su : (User) {Su:(User) -> () |%}-> () 522 | fun su(user) { do Su(user) } 523 | 524 | sig sessionmgr : (User, () {Ask:String, Su:(User) -> () |%}-> a) { |%}-> a 525 | fun sessionmgr(user, m) { 526 | env(user, fun() { 527 | handle(m()) { 528 | case Return(ans) -> ans 529 | case Su(user', resume) -> 530 | env(user', fun() { resume(()) }) 531 | } 532 | }) 533 | } 534 | 535 | #{ 536 | #! 537 | # 538 | # Multiple user sessions example 539 | # 540 | sig example3 : () -> (Int, File) 541 | fun example3() { 542 | basicIO(fun() { 543 | sessionmgr(Root, fun() { 544 | status(fun() { 545 | su(Alice); echo(whoami()); echo(" "); 546 | su(Bob); echo(whoami()); echo(" "); 547 | su(Root); echo(whoami()) 548 | }) 549 | }) 550 | }) 551 | } 552 | #? 553 | #} 554 | 555 | sig example3 : () { |%}-> (Int, File) 556 | fun example3() { 557 | basicIO(fun() { 558 | sessionmgr(Root, fun() { 559 | status(fun() { 560 | su(Alice); echo(whoami()); echo(" "); 561 | su(Bob); echo(whoami()); echo(" "); 562 | su(Root); echo(whoami()) 563 | }) 564 | }) 565 | }) 566 | } 567 | 568 | #! 569 | # 570 | # Nondeterminism: Multi-tasking (1) 571 | # 572 | # From the man pages. 573 | # 574 | # Description 575 | # fork() creates a new process by duplicating the calling process. The 576 | # new process is referred to as the child process. The calling process 577 | # is referred to as the parent process. 578 | # 579 | # Return value 580 | # On success, the PID of the child process is returned in the parent, 581 | # and 0 is returned in the child. 582 | # 583 | #? 584 | 585 | #! 586 | # 587 | # Nondeterminism: Multi-tasking (2) 588 | # 589 | # Fork idiom 590 | # 591 | # if (fork() > 0) parent's code 592 | # else child's code 593 | # 594 | # Let's simplify fork such that it returns a boolean: true for parent, 595 | # false for child. 596 | # 597 | #? 598 | 599 | #{ 600 | #! 601 | # 602 | # Nondeterminism: Multi-tasking (3) 603 | # 604 | sig fork : () {Fork:Bool}-> Bool 605 | fun fork() { do Fork } 606 | 607 | sig nondet : (() {Fork:Bool}-> a) -> [a] 608 | fun nondet(m) { 609 | handle(m()) { 610 | case ans -> [ans] 611 | case resume> -> resume(true) ++ resume(false) 612 | } 613 | } 614 | #? 615 | #} 616 | 617 | sig fork : () {Fork:Bool |_}-> Bool 618 | fun fork() { do Fork } 619 | 620 | sig nondet : (() {Fork:Bool |%}-> a) { |%}-> [a] 621 | fun nondet(m) { 622 | handle(m()) { 623 | case Return(ans) -> [ans] 624 | case Fork(resume) -> resume(true) ++ resume(false) 625 | } 626 | } 627 | 628 | #{ 629 | #! 630 | # 631 | # Nondeterminism: Example (1) 632 | # 633 | sig ritchie : () {Write:(FileDescr, String) -> ()}-> () 634 | fun ritchie() { 635 | echo("UNIX is basically "); 636 | echo("a simple operating system, "); 637 | echo("but "); 638 | echo("you have to be a genius to understand the simplicity.\n") 639 | } 640 | 641 | sig hamlet : () {Write:(FileDescr, String) -> ()}-> () 642 | fun hamlet() { 643 | echo("To be, or not to be,\n"); 644 | echo("that is the question:\n"); 645 | echo("Whether 'tis nobler in the mind to suffer\n") 646 | } 647 | #? 648 | #} 649 | sig ritchie : () {Write:(FileDescr, String) -> () |%}-> () 650 | fun ritchie() { 651 | echo("UNIX is basically "); 652 | echo("a simple operating system, "); 653 | echo("but "); 654 | echo("you have to be a genius to understand the simplicity.\n") 655 | } 656 | 657 | sig hamlet : () {Write:(FileDescr, String) -> () |%}-> () 658 | fun hamlet() { 659 | echo("To be, or not to be,\n"); 660 | echo("that is the question:\n"); 661 | echo("Whether 'tis nobler in the mind to suffer\n") 662 | } 663 | 664 | #{ 665 | #! 666 | # 667 | # Nondeterminism: Example (2) 668 | # 669 | sig example4 : () -> ([Int], File) 670 | fun example4() { 671 | basicIO(fun() { 672 | nondet(fun() { 673 | sessionmgr(Root, fun() { 674 | status(fun() { 675 | if (fork()) { 676 | su(Alice); 677 | ritchie() 678 | } else { 679 | su(Bob); 680 | hamlet() 681 | } 682 | }) 683 | }) 684 | }) 685 | }) 686 | } 687 | #? 688 | #} 689 | 690 | sig example4 : () { |%}-> ([Int], File) 691 | fun example4() { 692 | basicIO(fun() { 693 | nondet(fun() { 694 | sessionmgr(Root, fun() { 695 | status(fun() { 696 | if (fork()) { 697 | su(Alice); 698 | ritchie() 699 | } else { 700 | su(Bob); 701 | hamlet() 702 | } 703 | }) 704 | }) 705 | }) 706 | }) 707 | } 708 | 709 | # 710 | # 711 | # Mathematically well-founded nondeterminism 712 | # 713 | # The handler `nondet` is _exactly_ the handler Plotkin and Pretnar (2013) 714 | # give for nondeterminism 715 | # It satisfies the usual (semi-lattice) equations for nondeterministic choice, i.e. 716 | # 717 | # if (fork()) M else M = M 718 | # if (fork()) M else N = if (fork()) N else M 719 | # if (fork()) L else { if (fork()) M else N } = if (fork()) { if (fork()) L else M } else N 720 | # 721 | # 722 | 723 | #{ 724 | #! 725 | # 726 | # Interrupting processes 727 | # 728 | sig interrupt : () {Interrupt:()}-> () 729 | fun interrupt() { do Interrupt } 730 | 731 | # Process reification 732 | typename Pstate(a::Type, e::Eff) 733 | = [|Done:a 734 | |Paused:() -e-> Pstate(a, e)|]; 735 | 736 | 737 | sig reifyProcess : (() {Interrupt:() |e}-> a) -e-> Pstate(a, e) 738 | fun reifyProcess(m) { 739 | handle(m()) { 740 | case ans -> Done(ans) 741 | case resume> -> Paused(fun() { resume(()) }) 742 | } 743 | } 744 | #? 745 | #} 746 | 747 | sig interrupt : () {Interrupt:() |%}-> () 748 | fun interrupt() { do Interrupt } 749 | 750 | typename Pstate(a,e::Eff) 751 | = forall q::Presence. 752 | [|Done:a 753 | |Paused:() {Interrupt{q} |e}-> Pstate(a, { |e})|]; 754 | 755 | 756 | sig reifyProcess : (() {Interrupt:() |%}-> a) { |%}-> Pstate(a, { |%}) 757 | fun reifyProcess(m) { 758 | handle(m()) { 759 | case Return(ans) -> Done(ans) 760 | case Interrupt(resume) -> Paused(fun() { resume(()) }) 761 | } 762 | } 763 | 764 | #{ 765 | #! 766 | # 767 | # Time-sharing via interrupts 768 | # 769 | sig schedule : ([Pstate(a, {Fork:Bool})]) -> [a] 770 | fun schedule(ps) { 771 | fun schedule(ps, done) { 772 | switch (ps) { 773 | case [] -> done 774 | case Done(res) :: ps' -> 775 | schedule(ps', res :: done) 776 | case Paused(resume) :: ps' -> 777 | schedule(ps' ++ nondet(resume), done) 778 | } 779 | } 780 | schedule(ps, []) 781 | } 782 | 783 | sig timeshare : (() {Fork:Bool,Interrupt:()}-> a) -> [a] 784 | fun timeshare(m) { 785 | var p = Paused(fun() { reifyProcess(m) }); 786 | schedule([p]) 787 | } 788 | #? 789 | #} 790 | 791 | sig schedule : ([Pstate(a, { Fork:Bool |%})]) { |%}~> [a] 792 | fun schedule(ps) { 793 | fun schedule(ps, done) { 794 | switch (ps) { 795 | case [] -> done 796 | case Done(res) :: ps' -> 797 | schedule(ps', res :: done) 798 | case Paused(resume) :: ps' -> 799 | schedule(ps' ++ nondet(resume), done) 800 | } 801 | } 802 | schedule(ps, []) 803 | } 804 | 805 | sig timeshare : (() {Fork:Bool,Interrupt:() |%}-> a) { |%}-> [a] 806 | fun timeshare(m) { 807 | var p = Paused(fun() { reifyProcess(m) }); 808 | schedule([p]) 809 | } 810 | 811 | #{ 812 | #! 813 | # 814 | # Injecting interrupts 815 | # 816 | # First idea: external source injects interrupts (Ahman and Pretnar (2021)) 817 | # 818 | # Second idea: bundle interrupts with other operations 819 | sig echo' : (FileDescr,String) {Interrupt:(), Write:(FileDescr,String) -> ()}-> () 820 | fun echo'(fd, cs) { interrupt(); do Write(fd, cs) } 821 | # 822 | # Third idea: overload interpretations of operations 823 | sig interruptWrite : (() {Write:(FileDescr,String) -> ()}-> a) 824 | {Interrupt:(),Write:(FileDescr,String) -> ()}-> a 825 | fun interruptWrite(m) { 826 | handle(m()) { 827 | case ans -> ans 828 | case resume> -> 829 | interrupt(); resume(do Write(fd, cs)) 830 | } 831 | } 832 | #? 833 | #} 834 | 835 | sig interruptWrite : (() {Write:(FileDescr,String) -> () |%}-> a) 836 | {Write:(FileDescr,String) -> () |%}-> a 837 | fun interruptWrite(m) { 838 | handle(m()) { 839 | case Return(res) -> res 840 | case Write(fd, cs, resume) -> 841 | interrupt(); resume(do Write(fd, cs)) 842 | } 843 | } 844 | 845 | #{ 846 | #! 847 | # 848 | # Time-sharing example 849 | # 850 | sig example5 : () -> ([Int], File) 851 | fun example5() { 852 | basicIO(fun() { 853 | timeshare(fun() { 854 | interruptWrite(fun() { 855 | sessionmgr(Root, fun() { 856 | status(fun() { 857 | if (fork()) { 858 | su(Alice); 859 | ritchie() 860 | } else { 861 | su(Bob); 862 | hamlet() 863 | } 864 | }) 865 | }) 866 | }) 867 | }) 868 | }) 869 | } 870 | #? 871 | #} 872 | sig example5 : () { |%}-> ([Int], File) 873 | fun example5() { 874 | basicIO(fun() { 875 | timeshare(fun() { 876 | interruptWrite(fun() { 877 | sessionmgr(Root, fun() { 878 | status(fun() { 879 | if (fork()) { 880 | su(Alice); 881 | ritchie() 882 | } else { 883 | su(Bob); 884 | hamlet() 885 | } 886 | }) 887 | }) 888 | }) 889 | }) 890 | }) 891 | } 892 | 893 | #{ 894 | #! 895 | # 896 | # State: File I/O 897 | # 898 | # Generic state handling 899 | sig get : () {Get:s}-> s 900 | fun get() { do Get } 901 | 902 | sig put : (s) {Put:(s) -> ()}-> () 903 | fun put(st) { do Put(st) } 904 | 905 | sig runState : (s, () {Get:() -> s,Put:(s) -> ()}-> a) -> (a, s) 906 | fun runState(st0, m) { 907 | var f = handle(m()) { 908 | case ans -> fun(st) { (ans, st) } 909 | case resume> -> fun(st) { resume(st)(st) } 910 | case resume> -> fun(_) { resume(())(st') } 911 | }; 912 | f(st0) 913 | } 914 | #? 915 | #} 916 | 917 | sig get : () {Get:s |_}-> s 918 | fun get() { do Get } 919 | 920 | sig put : (s) {Put:(s) -> () |_}-> () 921 | fun put(st) { do Put(st) } 922 | 923 | sig runState : (s, () {Get:() -> s,Put:(s) -> () |%}-> a) { |%}-> (a, s) 924 | fun runState(st0, m) { 925 | var f = handle(m()) { 926 | case Return(x) -> fun(st) { (x, st) } 927 | case Get(resume) -> fun(st) { resume(st)(st) } 928 | case Put(st',resume) -> fun(_) { resume(())(st') } 929 | }; 930 | f(st0) 931 | } 932 | 933 | #{ 934 | #! 935 | # 936 | # State: Example 937 | # 938 | sig incr : () {Get:Int,Put:(Int) -> ()}-> () 939 | fun incr() { put(get() + 1) } 940 | 941 | sig example6 : () -> ((), Int) 942 | fun example6() { 943 | runState(41, incr) 944 | } 945 | #? 946 | #} 947 | 948 | sig incr : () {Get:Int,Put:(Int) -> () |%}-> () 949 | fun incr() { put(get() + 1) } 950 | 951 | sig example6 : () { |%}-> ((), Int) 952 | fun example6() { 953 | runState(41, incr) 954 | } 955 | 956 | #! 957 | # 958 | # Basic Serial File System (BSFS) 959 | # 960 | # Directory I-List Data region 961 | # +----------------+ +-------+ +--------------------------+ 962 | # | "hamlet" |------> | 2 |---> | "To be, or not to be..." | 963 | # +----------------+ / +-------+ +--------------------------+ 964 | # | "richtie.txt" |------> | 1 |---> | "UNIX is basically..." | 965 | # +----------------+ / +-------+ +--------------------------+ 966 | # | ... | | | ... | | ... | 967 | # +----------------+ | +-------+ +--------------------------+ 968 | # | "stdout" |------> | 1 |---> | "" | 969 | # +----------------+ | +-------+ +--------------------------+ 970 | # | ... | / 971 | # +----------------+ / 972 | # | "act3" |--- 973 | # +----------------+ 974 | # 975 | # Simplifications: 976 | # - Operating directly on inode pointers 977 | # - Reads and writes will be serial 978 | # 979 | #? 980 | 981 | #{ 982 | #! 983 | # 984 | # BSFS structures 985 | # 986 | typename INode = (loc:Int,lno:Int); 987 | typename IList = [(Int, INode)]; # INode index -> INode 988 | typename Directory = [(String, Int)]; # Filename -> INode index 989 | typename DataRegion = [(Int, File)]; # Loc -> File 990 | 991 | typename FileSystem = (dir:Directory,dregion:DataRegion,inodes:IList 992 | ,lnext:Int ,inext:Int ); 993 | 994 | sig fsys0 : FileSystem 995 | var fsys0 = ( dir = [("stdout", 0)] 996 | , inodes = [(0, (loc=0, lno=1))] 997 | , dregion = [(0, "")] 998 | , lnext = 1, inext = 1 ); 999 | 1000 | 1001 | # Utility functions 1002 | sig lookup : (a, [(a, b)]) {Fail:Zero}-> b 1003 | var lookup = lookup; 1004 | 1005 | sig withDefault : (a, () {Fail:Zero}-> a) -> a 1006 | fun withDefault(d, m) { 1007 | handle(m()) { 1008 | case ans -> ans 1009 | case -> d 1010 | } 1011 | } 1012 | #? 1013 | #} 1014 | 1015 | typename INode = (loc:Int,lno:Int); 1016 | typename IList = [(Int, INode)]; # INode index -> INode 1017 | typename Directory = [(String, Int)]; # Filename -> INode index 1018 | typename DataRegion = [(Int, File)]; # Loc -> File 1019 | 1020 | typename FileSystem = (dir:Directory,dregion:DataRegion,inodes:IList 1021 | ,lnext:Int ,inext:Int ); 1022 | 1023 | sig fsys0 : FileSystem 1024 | var fsys0 = ( dir = [("stdout", 0)] 1025 | , inodes = [(0, (loc=0, lno=1))] 1026 | , dregion = [(0, "")] 1027 | , lnext = 1, inext = 1 ); 1028 | 1029 | 1030 | # Utility functions 1031 | sig lookup : (a, [(a, b)]) {Fail:Zero |%}-> b 1032 | var lookup = lookup; 1033 | 1034 | sig withDefault : (a, () {Fail:Zero |%}-> a) { |%}-> a 1035 | fun withDefault(x', m) { 1036 | handle(m()) { 1037 | case Return(x) -> x 1038 | case Fail(_) -> x' 1039 | } 1040 | } 1041 | 1042 | sig fwrite : (Int, String, FileSystem) {Fail:Zero |%}-> FileSystem 1043 | fun fwrite(ino, cs, fsys) { 1044 | var inode = lookup(ino, fsys.inodes); 1045 | var file = lookup(inode.loc, fsys.dregion); 1046 | var file' = file ^^ cs; 1047 | (fsys with dregion = modify(inode.loc, file', fsys.dregion)) 1048 | } 1049 | 1050 | sig fread : (Int, FileSystem) {Fail:Zero |%}-> String 1051 | fun fread(ino, fsys) { 1052 | var inode = lookup(ino, fsys.inodes); 1053 | lookup(inode.loc, fsys.dregion) 1054 | } 1055 | 1056 | #{ 1057 | #! 1058 | # 1059 | # Handling BSFS operations: file reading and writing 1060 | # 1061 | sig fwrite : (FileDescr, String, FileSystem) {Fail:Zero}-> FileSystem 1062 | var fwrite = fwrite; 1063 | sig fread : (FileDescr, FileSystem) {Fail:Zero}-> String 1064 | var fread = fread; 1065 | 1066 | sig fileRW : ( () { Read :(FileDescr) -> Option(String) 1067 | , Write:(FileDescr, String) -> () }-> a ) 1068 | {Get:FileSystem,Put:(FileSystem) -> ()}-> a 1069 | fun fileRW(m) { 1070 | handle(m()) { 1071 | case ans -> ans 1072 | case resume> -> 1073 | var cs = withDefault(None, fun() { 1074 | Some(fread(fd, get())) 1075 | }); resume(cs) 1076 | case resume> -> 1077 | withDefault((), fun() { 1078 | var fsys = fwrite(fd, cs, get()); 1079 | put(fsys) 1080 | }); resume(()) 1081 | } 1082 | } 1083 | #? 1084 | #} 1085 | 1086 | sig fileRW : ( () { Read :(FileDescr) -> Option(String) 1087 | , Write:(FileDescr, String) -> () |%}-> a ) 1088 | {Get:FileSystem,Put:(FileSystem) -> () |%}-> a 1089 | fun fileRW(m) { 1090 | handle(m()) { 1091 | case Return(ans) -> ans 1092 | case Read(fd, resume) -> 1093 | var cs = withDefault(None, fun() { 1094 | Some(fread(fd, get())) 1095 | }); resume(cs) 1096 | case Write(fd, cs, resume) -> 1097 | withDefault((), fun() { 1098 | var fsys = fwrite(fd, cs, get()); 1099 | put(fsys) 1100 | }); resume(()) 1101 | } 1102 | } 1103 | 1104 | sig fopen : (String, FileSystem) {Fail:Zero |%}-> FileDescr 1105 | fun fopen(fname, fsys) { lookup(fname, fsys.dir) } 1106 | 1107 | sig fcreate : (String, FileSystem) {Fail:Zero |%}-> (FileDescr, FileSystem) 1108 | fun fcreate(fname, fsys) { 1109 | if (has(fname, fsys.dir)) { 1110 | var ino = fopen(fname, fsys); 1111 | # Truncate file 1112 | var inode = lookup(ino, fsys.inodes); 1113 | var dregion = modify(inode.loc, "", fsys.dregion); 1114 | (ino, (fsys with =dregion)) 1115 | } else { 1116 | var loc = fsys.lnext; 1117 | var dregion = (loc, "") :: fsys.dregion; 1118 | 1119 | var ino = fsys.inext; 1120 | var inode = (loc=loc,lno=1); 1121 | var inodes = (ino, inode) :: fsys.inodes; 1122 | 1123 | var dir = (fname, ino) :: fsys.dir; 1124 | (ino, (=dir, =dregion, =inodes, lnext=loc+1, inext=ino+1)) 1125 | } 1126 | } 1127 | 1128 | #{ 1129 | #! 1130 | # 1131 | # BSFS operation: file opening and creation 1132 | # 1133 | sig fopen : (String, FileSystem) {Fail:Zero}-> FileDescr 1134 | var fopen = fopen; 1135 | sig fcreate : (String, FileSystem) {Fail:Zero}-> (FileDescr, FileSystem) 1136 | var fcreate = fcreate; 1137 | 1138 | sig fileOC : ( () { Open :(String) -> Option(FileDescr) 1139 | , Create:(String) -> Option(FileDescr) }-> a ) 1140 | {Get:FileSystem,Put:(FileSystem) -> () }-> a 1141 | fun fileOC(m) { 1142 | handle(m()) { 1143 | case ans -> ans 1144 | case resume> -> 1145 | var fd = withDefault(None, fun() { 1146 | Some(fopen(fname, get())) 1147 | }); resume(fd) 1148 | case resume> -> 1149 | var fd = withDefault(None, fun() { 1150 | var (fd, fsys) = fcreate(fname, get()); 1151 | put(fsys); Some(fd) 1152 | }); resume(fd) 1153 | } 1154 | } 1155 | #? 1156 | #} 1157 | 1158 | sig fileOC : ( () { Open :(String) -> Option(FileDescr) 1159 | , Create:(String) -> Option(FileDescr) |%}-> a ) 1160 | {Get:FileSystem,Put:(FileSystem) -> () |%}-> a 1161 | fun fileOC(m) { 1162 | handle(m()) { 1163 | case Return(ans) -> ans 1164 | case Open(fname, resume) -> 1165 | var fd = withDefault(None, fun() { 1166 | Some(fopen(fname, get())) 1167 | }); resume(fd) 1168 | case Create(fname, resume) -> 1169 | var fd = withDefault(None, fun() { 1170 | var (fd, fsys) = fcreate(fname, get()); 1171 | put(fsys); Some(fd) 1172 | }); resume(fd) 1173 | } 1174 | } 1175 | 1176 | #{ 1177 | #! 1178 | # 1179 | # BSFS version 0 1180 | # 1181 | sig bsfs0 : ( () { Open :(String) -> Option(FileDescr) 1182 | , Create:(String) -> Option(FileDescr) 1183 | , Read :(FileDescr) -> Option(String) 1184 | , Write:(FileDescr, String) -> () }-> a ) 1185 | {Get:FileSystem,Put:(FileSystem) -> ()}-> a 1186 | fun bsfs0(m) { 1187 | fileOC(fun() { 1188 | fileRW(m) 1189 | }) 1190 | } 1191 | #? 1192 | #} 1193 | 1194 | sig bsfs0 : ( () { Open :(String) -> Option(FileDescr) 1195 | , Create:(String) -> Option(FileDescr) 1196 | , Read :(FileDescr) -> Option(String) 1197 | , Write:(FileDescr, String) -> () |%}-> a ) 1198 | {Get:FileSystem,Put:(FileSystem) -> () |%}-> a 1199 | fun bsfs0(m) { 1200 | fileOC(fun() { 1201 | fileRW(m) 1202 | }) 1203 | } 1204 | 1205 | #{ 1206 | #! 1207 | # 1208 | # Stream redirection 1209 | # 1210 | sig > : (() -> a, String) 1211 | { Create:(String) -> Option(FileDescr) 1212 | , Exit : (Int) -> Zero 1213 | , Write :(FileDescr,String) -> () }-> a 1214 | op f > fname { 1215 | var fd = switch (do Create(fname)) { 1216 | case None -> exit(-1) 1217 | case Some(fd) -> fd 1218 | }; handle(f()) { 1219 | case ans -> ans 1220 | case resume> -> 1221 | resume(do Write(fd, cs)) 1222 | } 1223 | } 1224 | #? 1225 | #} 1226 | 1227 | sig >- : (() { |%}-> a, String) 1228 | { Create:(String) -> Option(FileDescr) 1229 | , Exit : (Int) -> Zero 1230 | , Write :(FileDescr,String) -> () |%}-> a 1231 | op f >- fname { 1232 | var fd = switch (do Create(fname)) { 1233 | case None -> exit(-1) 1234 | case Some(fd) -> fd 1235 | }; handle(f()) { 1236 | case Return(x) -> x 1237 | case Write(_, cs, resume) -> 1238 | resume(do Write(fd, cs)) 1239 | } 1240 | } 1241 | 1242 | #{ 1243 | #! 1244 | # 1245 | # Crude copy 1246 | # 1247 | sig ccp : (String, String) { Create:(String) -> Option(FileDescr) 1248 | , Exit :(Int) -> Zero 1249 | , Read :(FileDescr) -> Option(String) 1250 | , Open :(String) -> Option(FileDescr) 1251 | , Write :(FileDescr,String) -> () }-> () 1252 | fun ccp(src, dst) { 1253 | var srcfd = switch (do Open(src)) { 1254 | case None -> exit(-1) 1255 | case Some(fd) -> fd 1256 | }; 1257 | switch (do Read(srcfd)) { 1258 | case None -> exit(-1) 1259 | case Some(cs) -> fun() {echo(cs)} > dst 1260 | } 1261 | } 1262 | #? 1263 | #} 1264 | 1265 | sig ccp : (String, String) { Create:(String) -> Option(FileDescr) 1266 | , Exit :(Int) -> Zero 1267 | , Read :(FileDescr) -> Option(String) 1268 | , Open :(String) -> Option(FileDescr) 1269 | , Write :(FileDescr,String) -> () |%}-> () 1270 | fun ccp(src, dst) { 1271 | var srcfd = switch (do Open(src)) { 1272 | case None -> exit(-1) 1273 | case Some(fd) -> fd 1274 | }; 1275 | switch (do Read(srcfd)) { 1276 | case None -> exit(-1) 1277 | case Some(cs) -> fun() {echo(cs)} >- dst 1278 | } 1279 | } 1280 | 1281 | #{ 1282 | #! 1283 | # 1284 | # Plugging everything together 1285 | # 1286 | sig example7 : () -> ([Int], FileSystem) 1287 | fun example7() { 1288 | runState(fsys0, fun() { 1289 | bsfs0(fun() { 1290 | timeshare(fun() { 1291 | interruptWrite(fun() { 1292 | sessionmgr(Root, fun() { 1293 | status(fun() { 1294 | if (fork()) { 1295 | su(Alice); 1296 | ritchie > "ritchie.txt" 1297 | } else { 1298 | su(Bob); 1299 | hamlet > "hamlet"; 1300 | ccp("hamlet", "act3") 1301 | } 1302 | }) 1303 | }) 1304 | }) 1305 | }) 1306 | }) 1307 | }) 1308 | } 1309 | #? 1310 | #} 1311 | 1312 | sig example7 : () { |%}-> ([Int], FileSystem) 1313 | fun example7() { 1314 | runState(fsys0, fun() { 1315 | bsfs0(fun() { 1316 | timeshare(fun() { 1317 | interruptWrite(fun() { 1318 | sessionmgr(Root, fun() { 1319 | status(fun() { 1320 | if (fork()) { 1321 | su(Alice); 1322 | ritchie >- "ritchie.txt" 1323 | } else { 1324 | su(Bob); 1325 | hamlet >- "hamlet"; 1326 | ccp("hamlet", "act3") 1327 | } 1328 | }) 1329 | }) 1330 | }) 1331 | }) 1332 | }) 1333 | }) 1334 | } 1335 | 1336 | #! 1337 | # 1338 | # Conclusion 1339 | # 1340 | # + Effect handlers are a versatile programming abstraction 1341 | # + Possible to retrofit legacy code with new functionality 1342 | # + Operating systems can be explained in terms of handlers 1343 | # + "Every problem can be solved by adding another handler" 1344 | # 1345 | # See my PhD dissertation[1] for an implementation of UNIX fork, usage of 1346 | # shallow handlers to implement a more UNIX-y shell environment. 1347 | # 1348 | # [1] "Foundations for Programming and Implementing Effect Handlers", 1349 | # Daniel Hillerström, PhD dissertation, The University of 1350 | # Edinburgh, Scotland, UK, 2021. 1351 | # 1352 | #? 1353 | -------------------------------------------------------------------------------- /code/unix-plug2020.links: -------------------------------------------------------------------------------- 1 | ## Prelude 2 | typename Option(a) = [|None|Some:a|]; 3 | 4 | sig todo : (String) ~> a 5 | fun todo(s) { error("TODO: " ^^ s) } 6 | 7 | sig fail : () {Fail:Zero |_}-> a 8 | fun fail() { switch (do Fail) { } } 9 | 10 | sig lookup : (a, [(a, b)]) {Fail:Zero |_}~> b 11 | fun lookup(k, kvs) { 12 | switch (kvs) { 13 | case [] -> fail() 14 | case (k', v) :: kvs' -> 15 | if (k == k') v 16 | else lookup(k, kvs') 17 | } 18 | } 19 | 20 | sig modify : (a, b, [(a, b)]) ~> [(a, b)] 21 | fun modify(k, v, kvs) { 22 | switch (kvs) { 23 | case [] -> [] 24 | case (k', v') :: kvs' -> 25 | if (k == k') (k, v) :: kvs' 26 | else (k', v') :: modify(k, v, kvs') 27 | } 28 | } 29 | 30 | sig remove : (a, [(a, b)]) ~> [(a, b)] 31 | fun remove(k, kvs) { 32 | switch (kvs) { 33 | case [] -> [] 34 | case (k', v') :: kvs' -> 35 | if (k == k') kvs' 36 | else (k', v') :: remove(k, kvs') 37 | } 38 | } 39 | 40 | sig has : (a, [(a, b)]) ~> Bool 41 | fun has(k, kvs) { 42 | switch (kvs) { 43 | case [] -> false 44 | case (k', _) :: kvs' -> 45 | k == k' || has(k, kvs') 46 | } 47 | } 48 | 49 | #! 50 | # 51 | # UNIX in 50 lines of code or less 52 | # Daniel Hillerström 53 | # Laboratory for Foundations of Computer Science 54 | # The University of Edinburgh 55 | # 56 | # PLUG talk 57 | # University of Glasgow 58 | # December 8, 2020 59 | # 60 | # https://dhil.net/research/ 61 | # 62 | #? 63 | 64 | #! 65 | # 66 | # What is an operating system? (very abstractly) 67 | # 68 | # An operating system responds to a collection of system calls 69 | # 70 | # Example tasks: 71 | # - Signalling errors 72 | # - Scheduling processes 73 | # - Reading/writing I/O 74 | #? 75 | 76 | #! 77 | # 78 | # What is an effect handler? (very abstractly) 79 | # 80 | # An effect handler responds a collection of abstract operation calls 81 | # 82 | # Example tasks: 83 | # - Signalling errors 84 | # - Scheduling processes 85 | # - Reading/writing I/O 86 | # 87 | # 88 | # 89 | # 90 | # 91 | #? 92 | 93 | #! 94 | # 95 | # What is an effect handler? (very abstractly) 96 | # 97 | # An effect handler responds a collection of abstract operation calls 98 | # 99 | # Example tasks: 100 | # - Signalling errors 101 | # - Scheduling processes 102 | # - Reading/writing I/O 103 | # 104 | # Thus an effect handler is an operating system (credit James McKinna) 105 | # (Kiselyov and Shan (2007) used delimited continuations to model 106 | # operating systems) 107 | # 108 | #? 109 | 110 | #! 111 | # 112 | # Objectives of this talk 113 | # 114 | # - Demonstrate the versatility of handlers 115 | # - Explain operating systems as the combination of 116 | # + Exceptions 117 | # + Dynamic binding 118 | # + Nondeterminism 119 | # + State 120 | # 121 | #? 122 | 123 | # 124 | # 125 | # What is UNIX? 126 | # 127 | # UNIX is an operating system designed by Ritchie and Thompson (1974) 128 | # 129 | # Components 130 | # - Commands (system calls) 131 | # + I/O interaction, user session management, inter-process 132 | # communication, etc 133 | # - Kernel (interpreter) 134 | # + Handling of I/O, managing user sessions, scheduling of 135 | # processes 136 | # - Development environment 137 | # + Compiler tool-chains (e.g. `cc`) 138 | # - Documentation 139 | # + manual pages (e.g. `man`) 140 | # 141 | # 142 | 143 | #! 144 | # 145 | # Key characteristics of UNIX (Ritchie & Thompson, 1974) 146 | # 147 | # - Support for multiple user sessions 148 | # - Time-sharing between processes 149 | # - "Everything is a file" 150 | # 151 | #? 152 | 153 | #! 154 | # 155 | # Basic I/O: Performing writes 156 | # 157 | typename File = String; 158 | typename FileDescr = Int; 159 | 160 | sig stdout : FileDescr 161 | var stdout = 1; 162 | 163 | sig echo : (String) {Write:(FileDescr,String) -> () |%}-> () 164 | fun echo(cs) { todo("implement echo") } 165 | #? 166 | 167 | #! 168 | # 169 | # Basic I/O: Handling writes 170 | # 171 | sig basicIO : ( () {Write:(FileDescr,String) -> () |%}-> a ) { |%}-> (a, File) 172 | fun basicIO(m) { 173 | todo("implement basicIO") 174 | } 175 | #? 176 | 177 | #! 178 | # 179 | # Basic I/O: Example 180 | # 181 | sig example0 : () { |%}-> ((), File) 182 | fun example0() { 183 | basicIO(fun() { 184 | echo("Hello"); echo("World") 185 | }) 186 | } 187 | #? 188 | 189 | #! 190 | # 191 | # Dynamic semantics of handlers 192 | # 193 | # (ret) handle(V) { case Return(x) -> N case ... } 194 | # ~> N[V/x] 195 | # 196 | # (op) handle(E[do Op(V)]) { case Op(p,r) -> N case ... } 197 | # ~> N[V/p 198 | # ,fun(x){ handle(E[x]) { case Op(p,r) -> N case ... }}/r] 199 | # (if Op \notin E) 200 | # 201 | #? 202 | 203 | #! 204 | # 205 | # Exceptions: Premature exits 206 | # 207 | sig exit : (Int) {Exit:(Int) -> Zero |%}-> a 208 | fun exit(n) { todo("implement exit") } 209 | #? 210 | 211 | #! 212 | # 213 | # Handling exits 214 | # 215 | sig status : (() {Exit:(Int) -> Zero |%}-> a) { |%}-> Int 216 | fun status(m) { 217 | todo("implement status") 218 | } 219 | #? 220 | 221 | #! 222 | # 223 | # Handling exits: Example 224 | # 225 | sig example1 : () { |%}-> (Int, File) 226 | fun example1() { 227 | basicIO(fun() { 228 | status(fun() { 229 | echo("dead"); exit(1); echo("code") 230 | }) 231 | }) 232 | } 233 | #? 234 | 235 | #! 236 | # 237 | # Dynamic binding: User-specific environments (1) 238 | # 239 | typename User = [|Alice|Bob|Root|]; 240 | 241 | sig whoami : () {Ask:String |%}-> String 242 | fun whoami() { do Ask } 243 | #? 244 | 245 | #! 246 | # 247 | # Dynamic binding: User-specific environments (2) 248 | # 249 | sig env : (User, () {Ask:String |%}-> a) { |%}-> a 250 | fun env(user, m) { 251 | handle(m()) { 252 | case Return(x) -> x 253 | case Ask(resume) -> 254 | switch (user) { 255 | case Alice -> resume("alice") 256 | case Bob -> resume("bob") 257 | case Root -> resume("root") 258 | } 259 | } 260 | } 261 | 262 | 263 | sig example2 : () { |%}-> String 264 | fun example2() { 265 | env(Root, whoami) 266 | } 267 | #? 268 | 269 | #! 270 | # 271 | # Aside: Dynamic binding with delimited continuations 272 | # 273 | # The idea of dynamic binding dates back to at least McCarthy (1960) 274 | # 275 | # Kiselyov, Shan, and Sabry (2006) demonstrated dynamic binding can be 276 | # simulated with delimited continuations 277 | # 278 | #? 279 | 280 | #! 281 | # 282 | # User session management 283 | # 284 | sig su : (User) {Su:(User) -> () |%}-> () 285 | fun su(user) { do Su(user) } 286 | 287 | sig sessionmgr : (User, () {Ask:String, Su:(User) -> () |%}-> a) { |%}-> a 288 | fun sessionmgr(user, m) { 289 | env(user, fun() { 290 | handle(m()) { 291 | case Return(x) -> x 292 | case Su(user', resume) -> 293 | env(user', fun() { resume(()) }) 294 | } 295 | }) 296 | } 297 | #? 298 | 299 | #! 300 | # 301 | # Multiple user sessions example 302 | # 303 | sig example3 : () { |%}-> (Int, File) 304 | fun example3() { 305 | basicIO(fun() { 306 | sessionmgr(Root, fun() { 307 | status(fun() { 308 | su(Alice); echo(whoami()); echo(" "); 309 | su(Bob); echo(whoami()); echo(" "); 310 | su(Root); echo(whoami()) 311 | }) 312 | }) 313 | }) 314 | } 315 | #? 316 | 317 | #! 318 | # 319 | # Nondeterminism: Multi-tasking (1) 320 | # 321 | # From the man pages. 322 | # 323 | # Description 324 | # fork() creates a new process by duplicating the calling process. The 325 | # new process is referred to as the child process. The calling process 326 | # is referred to as the parent process. 327 | # 328 | # Return value 329 | # On success, the PID of the child process is returned in the parent, 330 | # and 0 is returned in the child. 331 | # 332 | #? 333 | 334 | #! 335 | # 336 | # Nondeterminism: Multi-tasking (2) 337 | # 338 | sig fork : () {Fork:Bool |_}-> Bool 339 | fun fork() { do Fork } 340 | 341 | sig nondet : (() {Fork:Bool |%}-> a) { |%}-> [a] 342 | fun nondet(m) { 343 | handle(m()) { 344 | case Return(ans) -> todo("implement Return case") 345 | case Fork(resume) -> todo("implement Fork case") 346 | } 347 | } 348 | #? 349 | 350 | #! 351 | # 352 | # Nondeterminism: Example (1) 353 | # 354 | sig ritchie : () {Write:(FileDescr, String) -> () |%}-> () 355 | fun ritchie() { 356 | echo("UNIX is basically "); 357 | echo("a simple operating system, "); 358 | echo("but "); 359 | echo("you have to be a genius to understand the simplicity.\n") 360 | } 361 | 362 | sig hamlet : () {Write:(FileDescr, String) -> () |%}-> () 363 | fun hamlet() { 364 | echo("To be, or not to be,\n"); 365 | echo("that is the question:\n"); 366 | echo("Whether 'tis nobler in the mind to suffer\n") 367 | } 368 | #? 369 | 370 | #! 371 | # 372 | # Nondeterminism: Example (2) 373 | # 374 | sig example4 : () { |%}-> ([Int], File) 375 | fun example4() { 376 | basicIO(fun() { 377 | nondet(fun() { 378 | sessionmgr(Root, fun() { 379 | status(fun() { 380 | if (fork()) { 381 | su(Alice); 382 | ritchie() 383 | } else { 384 | su(Bob); 385 | hamlet() 386 | } 387 | }) 388 | }) 389 | }) 390 | }) 391 | } 392 | #? 393 | 394 | # 395 | # 396 | # Mathematically well-founded nondeterminism 397 | # 398 | # The handler `nondet` is _exactly_ the handler Plotkin and Pretnar (2013) 399 | # give for nondeterminism 400 | # It satisfies the usual (semi-lattice) equations for nondeterministic choice, i.e. 401 | # 402 | # if (fork()) M else M = M 403 | # if (fork()) M else N = if (fork()) N else M 404 | # if (fork()) L else { if (fork()) M else N } = if (fork()) { if (fork()) L else M } else N 405 | # 406 | # 407 | 408 | #! 409 | # 410 | # Interrupting processes 411 | # 412 | sig interrupt : () {Interrupt:() |%}-> () 413 | fun interrupt() { do Interrupt } 414 | 415 | # Process reification 416 | typename Pstate(a,e::Eff) 417 | = forall q::Presence. 418 | [|Done:a 419 | |Paused:() {Interrupt{q} |e}-> Pstate(a, { |e})|]; 420 | 421 | 422 | sig reifyProcess : (() {Interrupt:() |%}-> a) { |%}-> Pstate(a, { |%}) 423 | fun reifyProcess(m) { 424 | handle(m()) { 425 | case Return(ans) -> Done(ans) 426 | case Interrupt(resume) -> Paused(fun() { resume(()) }) 427 | } 428 | } 429 | #? 430 | 431 | #! 432 | # 433 | # Time-sharing via interrupts 434 | # 435 | sig schedule : ([Pstate(a, { Fork:Bool |%})]) { |%}~> [a] 436 | fun schedule(ps) { 437 | fun schedule(ps, done) { 438 | switch (ps) { 439 | case [] -> done 440 | case Done(res) :: ps' -> 441 | schedule(ps', res :: done) 442 | case Paused(resume) :: ps' -> 443 | schedule(ps' ++ nondet(resume), done) 444 | } 445 | } 446 | schedule(ps, []) 447 | } 448 | 449 | sig timeshare : (() {Fork:Bool,Interrupt:() |%}-> a) { |%}-> [a] 450 | fun timeshare(m) { 451 | var p = Paused(fun() { reifyProcess(m) }); 452 | schedule([p]) 453 | } 454 | #? 455 | 456 | #! 457 | # 458 | # Injecting interrupts 459 | # 460 | # First idea: external source injects interrupts (Ahman and Pretnar (2021)) 461 | # 462 | # Second idea: bundle interrupts with other operations 463 | sig echo' : (FileDescr,String) {Interrupt:(), Write:(FileDescr,String) -> () |%}-> () 464 | fun echo'(fd, cs) { interrupt(); do Write(fd, cs) } 465 | # 466 | # Third idea: overload interpretations of operations 467 | sig interruptWrite : (() {Write:(FileDescr,String) -> () |%}-> a) 468 | {Write:(FileDescr,String) -> () |%}-> a 469 | fun interruptWrite(m) { 470 | handle(m()) { 471 | case Return(res) -> res 472 | case Write(fd, cs, resume) -> 473 | interrupt(); resume(do Write(fd, cs)) 474 | } 475 | } 476 | #? 477 | 478 | #! 479 | # 480 | # Time-sharing example 481 | # 482 | sig example5 : () { |%}-> ([Int], File) 483 | fun example5() { 484 | basicIO(fun() { 485 | timeshare(fun() { 486 | interruptWrite(fun() { 487 | sessionmgr(Root, fun() { 488 | status(fun() { 489 | if (fork()) { 490 | su(Alice); 491 | ritchie() 492 | } else { 493 | su(Bob); 494 | hamlet() 495 | } 496 | }) 497 | }) 498 | }) 499 | }) 500 | }) 501 | } 502 | #? 503 | 504 | #! 505 | # 506 | # State: File I/O 507 | # 508 | # Generic state handling 509 | sig get : () {Get:s |_}-> s 510 | fun get() { do Get } 511 | 512 | sig put : (s) {Put:(s) -> () |_}-> () 513 | fun put(st) { do Put(st) } 514 | 515 | sig runState : (s, () {Get:() -> s,Put:(s) -> () |%}-> a) { |%}-> (a, s) 516 | fun runState(st0, m) { 517 | var f = handle(m()) { 518 | case Return(x) -> fun(st) { (x, st) } 519 | case Get(resume) -> fun(st) { resume(st)(st) } 520 | case Put(st',resume) -> fun(_) { resume(())(st') } 521 | }; 522 | f(st0) 523 | } 524 | #? 525 | 526 | #! 527 | # 528 | # State: Example 529 | # 530 | sig incr : () {Get:Int,Put:(Int) -> () |%}-> () 531 | fun incr() { put(get() + 1) } 532 | 533 | sig example6 : () { |%}-> ((), Int) 534 | fun example6() { 535 | runState(41, incr) 536 | } 537 | #? 538 | 539 | #! 540 | # 541 | # Basic Serial File System 542 | # 543 | # Directory I-List Data region 544 | # +----------------+ +-------+ +--------------------------+ 545 | # | "hamlet" |------> | 2 |---> | "To be, or not to be..." | 546 | # +----------------+ / +-------+ +--------------------------+ 547 | # | "richtie.txt" |------> | 1 |---> | "UNIX is basically..." | 548 | # +----------------+ / +-------+ +--------------------------+ 549 | # | ... | | | ... | | ... | 550 | # +----------------+ | +-------+ +--------------------------+ 551 | # | "stdout" |------> | 1 |---> | "" | 552 | # +----------------+ | +-------+ +--------------------------+ 553 | # | ... | / 554 | # +----------------+ / 555 | # | "act3" |--- 556 | # +----------------+ 557 | # 558 | # Simplifications: 559 | # - Operating directly on inode pointers 560 | # - Reads and writes will be serial 561 | # 562 | #? 563 | 564 | #! 565 | # 566 | # BSFS structures 567 | # 568 | typename INode = (loc:Int,lno:Int); 569 | typename IList = [(Int, INode)]; # INode index -> INode 570 | typename Directory = [(String, Int)]; # Filename -> INode index 571 | typename DataRegion = [(Int, File)]; # Loc -> File 572 | 573 | typename FileSystem = (dir:Directory,dregion:DataRegion,inodes:IList 574 | ,lnext:Int ,inext:Int ); 575 | 576 | sig fsys0 : FileSystem 577 | var fsys0 = ( dir = [("stdout", 0)] 578 | , inodes = [(0, (loc=0, lno=1))] 579 | , dregion = [(0, "")] 580 | , lnext = 1, inext = 1 ); 581 | 582 | 583 | # Utility functions 584 | sig lookup : (a, [(a, b)]) {Fail:Zero |%}-> b 585 | var lookup = lookup; 586 | 587 | sig withDefault : (a, () {Fail:Zero |%}-> a) { |%}-> a 588 | fun withDefault(x', m) { 589 | handle(m()) { 590 | case Return(x) -> x 591 | case Fail(_) -> x' 592 | } 593 | } 594 | #? 595 | 596 | sig fwrite : (Int, String, FileSystem) {Fail:Zero |%}-> FileSystem 597 | fun fwrite(ino, cs, fsys) { 598 | var inode = lookup(ino, fsys.inodes); 599 | var file = lookup(inode.loc, fsys.dregion); 600 | var file' = file ^^ cs; 601 | (fsys with dregion = modify(inode.loc, file', fsys.dregion)) 602 | } 603 | 604 | sig fread : (Int, FileSystem) {Fail:Zero |%}-> String 605 | fun fread(ino, fsys) { 606 | var inode = lookup(ino, fsys.inodes); 607 | lookup(inode.loc, fsys.dregion) 608 | } 609 | 610 | #! 611 | # 612 | # Handling BSFS operations: file reading and writing 613 | # 614 | sig fwrite : (FileDescr, String, FileSystem) {Fail:Zero |%}-> FileSystem 615 | var fwrite = fwrite; 616 | sig fread : (FileDescr, FileSystem) {Fail:Zero |%}-> String 617 | var fread = fread; 618 | 619 | sig fileRW : ( () { Read :(FileDescr) -> Option(String) 620 | , Write:(FileDescr, String) -> () |%}-> a ) 621 | {Get:FileSystem,Put:(FileSystem) -> () |%}-> a 622 | fun fileRW(m) { 623 | handle(m()) { 624 | case Return(ans) -> ans 625 | case Read(fd, resume) -> 626 | var cs = withDefault(None, fun() { 627 | Some(fread(fd, get())) 628 | }); resume(cs) 629 | case Write(fd, cs, resume) -> 630 | withDefault((), fun() { 631 | var fsys = fwrite(fd, cs, get()); 632 | put(fsys) 633 | }); resume(()) 634 | } 635 | } 636 | #? 637 | 638 | sig fopen : (String, FileSystem) {Fail:Zero |%}-> FileDescr 639 | fun fopen(fname, fsys) { lookup(fname, fsys.dir) } 640 | 641 | sig fcreate : (String, FileSystem) {Fail:Zero |%}-> (FileDescr, FileSystem) 642 | fun fcreate(fname, fsys) { 643 | if (has(fname, fsys.dir)) { 644 | var ino = fopen(fname, fsys); 645 | # Truncate file 646 | var inode = lookup(ino, fsys.inodes); 647 | var dregion = modify(inode.loc, "", fsys.dregion); 648 | (ino, (fsys with =dregion)) 649 | } else { 650 | var loc = fsys.lnext; 651 | var dregion = (loc, "") :: fsys.dregion; 652 | 653 | var ino = fsys.inext; 654 | var inode = (loc=loc,lno=1); 655 | var inodes = (ino, inode) :: fsys.inodes; 656 | 657 | var dir = (fname, ino) :: fsys.dir; 658 | (ino, (=dir, =dregion, =inodes, lnext=loc+1, inext=ino+1)) 659 | } 660 | } 661 | 662 | #! 663 | # 664 | # BSFS operation: file opening and creation 665 | # 666 | sig fopen : (String, FileSystem) {Fail:Zero |%}-> FileDescr 667 | var fopen = fopen; 668 | sig fcreate : (String, FileSystem) {Fail:Zero |%}-> (FileDescr, FileSystem) 669 | var fcreate = fcreate; 670 | 671 | sig fileOC : ( () { Open :(String) -> Option(FileDescr) 672 | , Create:(String) -> Option(FileDescr) |%}-> a ) 673 | {Get:FileSystem,Put:(FileSystem) -> () |%}-> a 674 | fun fileOC(m) { 675 | handle(m()) { 676 | case Return(ans) -> ans 677 | case Open(fname, resume) -> 678 | var fd = withDefault(None, fun() { 679 | Some(fopen(fname, get())) 680 | }); resume(fd) 681 | case Create(fname, resume) -> 682 | var fd = withDefault(None, fun() { 683 | var (fd, fsys) = fcreate(fname, get()); 684 | put(fsys); Some(fd) 685 | }); resume(fd) 686 | } 687 | } 688 | #? 689 | 690 | #! 691 | # 692 | # BSFS version 0 693 | # 694 | sig bsfs0 : ( () { Open :(String) -> Option(FileDescr) 695 | , Create:(String) -> Option(FileDescr) 696 | , Read :(FileDescr) -> Option(String) 697 | , Write:(FileDescr, String) -> () |%}-> a ) 698 | {Get:FileSystem,Put:(FileSystem) -> () |%}-> a 699 | fun bsfs0(m) { 700 | fileOC(fun() { 701 | fileRW(m) 702 | }) 703 | } 704 | #? 705 | 706 | #! 707 | # 708 | # Stream redirection 709 | # 710 | sig >- : (() { |%}-> a, String) 711 | { Create:(String) -> Option(FileDescr) 712 | , Exit : (Int) -> Zero 713 | , Write :(FileDescr,String) -> () |%}-> a 714 | op f >- fname { 715 | var fd = switch (do Create(fname)) { 716 | case None -> exit(-1) 717 | case Some(fd) -> fd 718 | }; handle(f()) { 719 | case Return(x) -> x 720 | case Write(_, cs, resume) -> 721 | resume(do Write(fd, cs)) 722 | } 723 | } 724 | #? 725 | 726 | #! 727 | # 728 | # Crude copy 729 | # 730 | sig ccp : (String, String) { Create:(String) -> Option(FileDescr) 731 | , Exit :(Int) -> Zero 732 | , Read :(FileDescr) -> Option(String) 733 | , Open :(String) -> Option(FileDescr) 734 | , Write :(FileDescr,String) -> () |%}-> () 735 | fun ccp(src, dst) { 736 | var srcfd = switch (do Open(src)) { 737 | case None -> exit(-1) 738 | case Some(fd) -> fd 739 | }; 740 | switch (do Read(srcfd)) { 741 | case None -> exit(-1) 742 | case Some(cs) -> fun() {echo(cs)} >- dst 743 | } 744 | } 745 | #? 746 | 747 | #! 748 | # 749 | # Plugging everything together 750 | # 751 | sig example7 : () { |%}-> ([Int], FileSystem) 752 | fun example7() { 753 | runState(fsys0, fun() { 754 | bsfs0(fun() { 755 | timeshare(fun() { 756 | sessionmgr(Root, fun() { 757 | status(fun() { 758 | if (fork()) { 759 | su(Alice); 760 | ritchie >- "ritchie.txt" 761 | } else { 762 | su(Bob); 763 | hamlet >- "hamlet"; 764 | ccp("hamlet", "act3") 765 | } 766 | }) 767 | }) 768 | }) 769 | }) 770 | }) 771 | } 772 | #? 773 | 774 | #! 775 | # 776 | # Conclusion 777 | # 778 | # Effect handlers are a versatile programming abstraction 779 | # Operating systems can be explained in terms of handlers 780 | # "Every problem can be solved by adding another handler" 781 | # 782 | #? 783 | -------------------------------------------------------------------------------- /code/unix.links: -------------------------------------------------------------------------------- 1 | # Tiny UNIX in Links. 2 | 3 | ## 4 | ## Functional utils 5 | ## 6 | typename Option(a) = [|None|Some:a|]; 7 | 8 | sig modify : (a, b, [(a, b)]) ~> [(a, b)] 9 | fun modify(x, y, xs) { 10 | switch (xs) { 11 | case [] -> [] 12 | case (x', y') :: xs' -> 13 | if (x == x') (x, y) :: xs' 14 | else (x', y') :: modify(x, y, xs') 15 | } 16 | } 17 | 18 | ### 19 | ### Generic queue 20 | ### 21 | module Queue { 22 | typename T(a) = (front:[a], rear:[a]); 23 | 24 | sig empty : T(a) 25 | var empty = (front=[], rear=[]); 26 | 27 | sig enqueue : (a, T(a)) -> T(a) 28 | fun enqueue(x, q) { 29 | (q with rear = x :: q.rear) 30 | } 31 | 32 | sig dequeue : (T(a)) ~> (Option(a), T(a)) 33 | fun dequeue(q) { 34 | switch(q.front) { 35 | case [] -> 36 | switch (q.rear) { 37 | case [] -> (None, q) 38 | case rear -> dequeue((front=reverse(rear),rear=[])) 39 | } 40 | case x :: xs -> (Some(x), (q with front = xs)) 41 | } 42 | } 43 | 44 | sig singleton : (a) -> T(a) 45 | fun singleton(x) { enqueue(x, empty) } 46 | } 47 | 48 | ## 49 | ## Environment 50 | ## 51 | sig getenv : (String) {Getenv:(String) -> String |_}-> String 52 | fun getenv(s) { do Getenv(s) } 53 | 54 | sig environment : ([(String,String)], () {Getenv:(String) -> String |e}~> a) {Getenv{_} |e}~> a 55 | fun environment(env', m) { 56 | handle(m()) { 57 | case Return(x) -> x 58 | case Getenv(s, resume) -> 59 | switch (lookup(s, env')) { 60 | case Nothing -> resume("") 61 | case Just(s') -> resume(s') 62 | } 63 | } 64 | } 65 | 66 | typename Environment = [(String, String)]; 67 | 68 | ## 69 | ## User management 70 | ## 71 | typename User = [|Root|Alice|Bob|]; 72 | typename EnvironmentStore = [(User, Environment)]; 73 | 74 | sig envs : [(User, Environment)] 75 | var envs = [ (Root , [("USER", "root") , ("UID", "0")]) 76 | , (Alice, [("USER", "alice"), ("UID", "1")]) 77 | , (Bob , [("USER", "bob") , ("UID", "2")]) 78 | ]; 79 | 80 | sig envOf : (User, [(User, Environment)]) ~> Environment 81 | fun envOf(user, envs) { 82 | switch (envs) { 83 | case [] -> error("No environment") # TODO: could interpret as something "fun" such as a kernel panic. 84 | case (user', env) :: envs -> 85 | if (user == user') env 86 | else envOf(user, envs) 87 | } 88 | } 89 | 90 | sig su : (User) {Su:(User) -> () |_}-> () 91 | fun su(user) { do Su(user) } 92 | 93 | sig usermgr : (User, EnvironmentStore, Comp(a, {Getenv:(String) -> String,Su:(User) -> () |e})) {Getenv{_},Su{_} |e}~> a 94 | fun usermgr(user, envs, m) { 95 | environment(envOf(user, envs), fun() { 96 | handle(m()) { 97 | case Su(user, resume) -> 98 | environment(envOf(user, envs), fun(){ resume(()) }) 99 | } 100 | }) 101 | } 102 | 103 | ## 104 | ## Basic IO 105 | ## 106 | typename FileDescr = Int; 107 | typename FileCursor = Int; 108 | 109 | module File { 110 | typename T = [String]; 111 | 112 | sig empty : T 113 | var empty = []; 114 | 115 | sig read : (FileCursor, T) ~> Option(String) 116 | fun read(start, file) { 117 | switch (drop(start, file)) { 118 | case [] -> None 119 | case x :: _ -> Some(x) 120 | } 121 | } 122 | 123 | sig write : (String, FileCursor, T) ~> T 124 | fun write(contents, fptr, file) { 125 | take(fptr, file) ++ [contents] ++ drop(fptr, file) 126 | } 127 | } 128 | 129 | sig stdout : FileDescr 130 | var stdout = 1; 131 | 132 | sig puts : (FileDescr,String) {Puts:(FileDescr,String) -> () |_}-> () 133 | fun puts(fd, s) { do Puts(fd, s) } 134 | 135 | sig basicIO : (Comp(a, {Puts:(FileDescr,String) -> () |e})) {Puts{_} |e}~> File.T 136 | fun basicIO(m) { 137 | handle(m()) { 138 | case Return(_) -> [] 139 | case Puts(_, s, resume) -> s :: resume(()) 140 | } 141 | } 142 | 143 | ## 144 | ## Generic state handling. 145 | ## 146 | sig get : () {Get:s |_}-> s 147 | fun get() { do Get } 148 | 149 | sig put : (s) {Put:(s) -> () |_}-> () 150 | fun put(st) { do Put(st) } 151 | 152 | sig runState : (s, Comp(a, {Get:() -> s,Put:(s) -> () |e})) {Get{_},Put{_} |e}~> (a, s) 153 | fun runState(st0, m) { 154 | var f = handle(m()) { 155 | case Return(x) -> fun(st) { (x, st) } 156 | case Get(resume) -> fun(st) { resume(st)(st) } 157 | case Put(st,resume) -> fun(_) { resume(())(st) } 158 | }; 159 | f(st0) 160 | } 161 | 162 | ## 163 | ## File IO 164 | ## 165 | sig stdin : FileDescr 166 | var stdin = 0; 167 | sig stderr : FileDescr 168 | var stderr = 2; 169 | 170 | sig eof : String 171 | var eof = "\x00"; 172 | 173 | typename Mode = [|Read|Write|]; 174 | 175 | typename FileDescr = Int; 176 | typename INode = (loc:Option(Int),refc:Int); 177 | 178 | typename INodeTable = [(INode, File.T)]; 179 | typename FileTable = [(Mode, INode)]; 180 | typename 181 | 182 | # sig gets : (FileDescr) {Gets:(FileDescr) -> String |_}-> String 183 | # fun gets(fd) { do Gets(fd) } 184 | 185 | # sig fopen : (Mode, String) {Fopen:(Mode, String) -> FileDescr |_}-> FileDescr 186 | # fun fopen(mode, filename) { do Fopen(mode, filename) } 187 | 188 | # sig fclose : (FileDescr) {Fclose:(FileDescr) -> () |_}-> () 189 | # fun fclose(fd) { do Fclose(fd) } 190 | 191 | # typename File = Queue.T(String); 192 | 193 | # sig emptyFile : File 194 | # var emptyFile = Queue.empty; 195 | 196 | # sig writeFile : (String, File) -> File 197 | # fun writeFile(s, file) { Queue.enqueue(s, file) } 198 | 199 | # sig readFile : (File) ~> (String, File) 200 | # fun readFile(file) { 201 | # switch (Queue.dequeue(file)) { 202 | # case (None, file) -> (eof, file) 203 | # case (Some(s), file) -> (s, file) 204 | # } 205 | # } 206 | 207 | # typename FileTable = [(FileDescr, File)]; 208 | # typename FileStore = [(String, FileDescr)]; 209 | # typename FileSystem = (next:Int,ft:FileTable,fs:FileStore); 210 | 211 | # sig defaultFileSystem : () -> FileSystem 212 | # fun defaultFileSystem() { 213 | # var defaultTable = [ (stdin , emptyFile) 214 | # , (stdout, emptyFile) 215 | # , (stderr, emptyFile) ]; 216 | 217 | # var defaultStore = [ ("stdin" , stdin) 218 | # , ("stdout", stdout) 219 | # , ("stderr", stderr) ]; 220 | 221 | # (next=3,ft=defaultTable,fs=defaultStore) 222 | # } 223 | 224 | # sig lookupFile : (FileDescr, FileSystem) ~> File 225 | # fun lookupFile(fd, fsys) { 226 | # switch (lookup(fd, fsys.ft)) { 227 | # case Nothing -> error("err: No such file(" ^^ intToString(fd) ^^ ")") 228 | # case Just(file) -> file 229 | # } 230 | # } 231 | 232 | # sig replaceFile : (FileDescr, File, FileSystem) ~> FileSystem 233 | # fun replaceFile(fd, file, fsys) { 234 | # var ft = modify(fd, file, fsys.ft); 235 | # (fsys with ft = ft) # TODO handle nonexistent file. 236 | # } 237 | 238 | # sig createFile : (String, FileSystem) -> (FileDescr, FileSystem) 239 | # fun createFile(filename, fsys) { 240 | # var fd = fsys.next; 241 | # (fd, (next = fd + 1, fs = (filename, fd) :: fsys.fs, ft = (fd, emptyFile) :: fsys.ft)) 242 | # } 243 | 244 | # sig openFile : (Mode, String, FileSystem) ~> (FileDescr, FileSystem) 245 | # fun openFile(mode, filename, fsys) { 246 | # var (fd, fsys') = switch (lookup(filename, fsys.fs)) { 247 | # case Nothing -> createFile(filename, fsys) 248 | # case Just(fd) -> (fd, fsys) 249 | # }; 250 | # switch (mode) { 251 | # case Create -> error("erase") 252 | # case Append -> (fd, fsys') 253 | # } 254 | # } 255 | 256 | # sig closeFile : (File) ~> File 257 | # fun closeFile((=front,=rear)) { 258 | # (front=front ++ reverse(rear), rear=[]) 259 | # } 260 | 261 | # sig allowState : (() {Get-,Put- |e}~> a) -> () {Get:s,Put:(s) -> () |e}~> a 262 | # fun allowState(f) { (f : (() {Get:s,Put:(s) -> () |e}~> a) <- (() {Get-,Put- |e}~> a)) } 263 | 264 | # 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 265 | # fun fileIO(m) { 266 | # handle(allowState(m)()) { 267 | # case Gets(fd, resume) -> 268 | # var fsys = get(); 269 | # var (ch, file) = readFile(lookupFile(fd, fsys)); 270 | # put(replaceFile(fd, file, fsys)); resume(ch) 271 | # case Puts(fd, ch, resume) -> 272 | # var fsys = get(); 273 | # var fsys' = replaceFile(fd, writeFile(ch, lookupFile(fd, fsys)), fsys); 274 | # put(fsys'); resume(()) 275 | # case Fopen(mode, filename, resume) -> 276 | # var fsys = get(); 277 | # var (fd, fsys') = openFile(mode, filename, fsys); 278 | # put(fsys'); resume(fd) 279 | # case Fclose(fd, resume) -> 280 | # var fsys = get(); 281 | # var fsys' = replaceFile(fd, closeFile(lookupFile(fd, fsys)), fsys); 282 | # put(fsys'); resume(()) 283 | # } 284 | # } 285 | 286 | # sig redirect : (Comp(a, {Puts:(FileDescr,String) -> () |e}), FileDescr) {Puts:(FileDescr,String) -> () |e}~> a 287 | # fun redirect(m, fd) { 288 | # handle(m()) { 289 | # case Puts(_,s,resume) -> resume(puts(fd, s)) 290 | # } 291 | # } 292 | 293 | ## 294 | ## Processes 295 | ## 296 | sig yield : () {Yield:() |_}-> () 297 | fun yield() { do Yield() } 298 | 299 | sig fork : () {Fork:Bool |_}-> Bool 300 | fun fork() { do Fork } 301 | 302 | mutual { 303 | typename Process(e::Eff) = forall p::Presence,q::Presence. [|R:(()) {Fork{p},Yield{q} |e}~> PList({ |e})|]; 304 | typename PList(e::Eff) = [Process({ |e})]; 305 | } 306 | 307 | sig runNext : (PList({ |e})) {Yield{_},Fork{_} |e}~> () 308 | fun runNext(pending) { 309 | switch (concatMap(fun(R(r)) { r(()) }, pending)) { 310 | case [] -> () 311 | case pending -> runNext(pending) 312 | } 313 | } 314 | 315 | sig timeshare : (Comp(a, {Fork:Bool,Yield:() |e})) {Fork{_},Yield{_} |e}~> PList({ |e}) 316 | fun timeshare(proc) { 317 | handle(proc()) { 318 | case Return(_) -> [] 319 | case Yield(resume) -> [R(resume)] 320 | case Fork(resume) -> 321 | resume(true) ++ resume(false) 322 | } 323 | } 324 | 325 | sig schedule : (Comp(a, {Fork:Bool,Yield:() |e})) {Fork{_},Yield{_} |e}~> () 326 | fun schedule(m) { 327 | runNext(timeshare(m)) 328 | } 329 | 330 | # sig replace : (() {Getenv{p},Exece:(() {Getenv:(String) -> String,Exece{q} |e}~> (), [(String, String)]) -> Zero |e}~> a) {Getenv{p},Exece{q} |e}~> () 331 | # fun replace(proc) { 332 | # handle(proc()) { 333 | # case Return(_) -> () 334 | # case Exece(f, env, _) -> 335 | # environment(env, f) 336 | # } 337 | # } 338 | 339 | ## 340 | ## Utilities 341 | ## 342 | sig echo : (String) {Puts:(FileDescr,String) -> (), Yield:() |_}-> () 343 | fun echo(s) { 344 | yield(); puts(stdout, s) 345 | } 346 | 347 | sig amiroot : () {Getenv:(String) -> String |_}-> Bool 348 | fun amiroot() { getenv("UID") == "0" } 349 | 350 | sig whoami : () {Getenv:(String) -> String |_}-> String 351 | fun whoami() { getenv("USER") } 352 | 353 | ## 354 | ## Example 355 | ## 356 | 357 | # Tags puts with the name of the current user. 358 | # sig provenance : (Comp(a, {Getenv:(String) -> String,Puts:(FileDescr,String) -> () |e})) {Getenv:(String) -> String,Puts:(FileDescr,String) -> () |e}~> a 359 | # fun provenance(m) { 360 | # handle(m()) { 361 | # case Puts(fd, s, resume) -> 362 | # var user = whoami(); 363 | # resume(do Puts(fd, user ^^ "> " ^^ s)) 364 | # } 365 | # } 366 | 367 | # # An example of everything plugged together: a time-shared 'Hello World'. 368 | # sig example : () {Fork:Bool,Getenv:(String) -> String,Su:(User) -> (),Puts:(FileDescr,String) -> (),Yield:() |_}~> () 369 | # fun example() { 370 | # var pid = fork(); 371 | # var () = { 372 | # if (pid) redirect(fun(){puts(stdout, "dummy")}, stderr) 373 | # else if (fork()) su(Alice) 374 | # else su(Bob) 375 | # }; 376 | # var user = whoami(); 377 | # puts(stdout, "Hello World!"); 378 | # var uid = getenv("UID"); 379 | # echo("My UID is " ^^ uid); 380 | # (if (amiroot()) { yield(); echo(user ^^ " is running as root.") } else ()); 381 | # echo("My home dir is /home/" ^^ user) 382 | # } 383 | 384 | # # Wiring of handlers. 385 | # sig init : () {Fork{_},Getenv{_},Su{_},Puts{_},Yield{_} |_}~> [String] 386 | # fun init() { 387 | # basicIO(fun() { 388 | # schedule(fun() { 389 | # usermgr(Root, envs, fun() { 390 | # provenance(example) 391 | # }) 392 | # }) 393 | # }) 394 | # } 395 | 396 | # sig example' : () {Fork:Bool,Fclose:(FileDescr) -> (),Fopen:(Mode,String) -> FileDescr,Getenv:(String) -> String,Su:(User) -> (),Puts:(FileDescr,String) -> (),Yield:() |_}~> () 397 | # fun example'() { 398 | # var pid = fork(); 399 | # var () = { 400 | # if (pid) redirect(fun(){puts(stdout, "dummy")}, stderr) 401 | # else if (fork()) su(Alice) 402 | # else su(Bob) 403 | # }; 404 | # var user = whoami(); 405 | # var fd = fopen(Append, user ^^ ".txt"); 406 | # puts(fd, "Hello World!"); 407 | # var uid = getenv("UID"); 408 | # echo("My UID is " ^^ uid); 409 | # (if (amiroot()) { yield(); echo(user ^^ " is running as root.") } else ()); 410 | # echo("My home dir is /home/" ^^ user); 411 | # fclose(fd) 412 | # } 413 | 414 | 415 | # sig init' : (FileSystem) {Fclose{_},Fopen{_},Fork{_},Get{_},Getenv{_},Gets{_},Put{_},Puts{_},Su{_},Yield{_}|_}~> ((), FileSystem) 416 | # fun init'(fsys) { 417 | # runState(fsys, fun() { 418 | # fileIO(fun() { 419 | # schedule(fun() { 420 | # usermgr(Root, envs, example') 421 | # }) 422 | # }) 423 | # }) 424 | # } 425 | -------------------------------------------------------------------------------- /code/unix2.links: -------------------------------------------------------------------------------- 1 | # Tiny UNIX revision 2. 2 | 3 | typename Option(a) = [|None|Some:a|]; 4 | 5 | sig fail : () {Fail:Zero |_}-> a 6 | fun fail() { switch (do Fail) {} } 7 | 8 | sig optionalise : (Comp(a, {Fail:Zero |e})) {Fail{_} |e}~> Option(a) 9 | fun optionalise(m) { 10 | handle(m()) { 11 | case Return(x) -> Some(x) 12 | case Fail(_) -> None 13 | } 14 | } 15 | 16 | sig withDefault : (a, Comp(a, {Fail:Zero |e})) {Fail{_} |e}~> a 17 | fun withDefault(x', m) { 18 | handle(m()) { 19 | case Return(x) -> x 20 | case Fail(_) -> x' 21 | } 22 | } 23 | 24 | sig lookup : (a, [(a, b)]) {Fail:Zero |_}~> b 25 | fun lookup(k, kvs) { 26 | switch (kvs) { 27 | case [] -> fail() 28 | case (k', v) :: kvs' -> 29 | if (k == k') v 30 | else lookup(k, kvs') 31 | } 32 | } 33 | 34 | sig modify : (a, b, [(a, b)]) ~> [(a, b)] 35 | fun modify(k, v, kvs) { 36 | switch (kvs) { 37 | case [] -> [] 38 | case (k', v') :: kvs' -> 39 | if (k == k') (k, v) :: kvs' 40 | else (k', v') :: modify(k, v, kvs') 41 | } 42 | } 43 | 44 | sig remove : (a, [(a, b)]) ~> [(a, b)] 45 | fun remove(k, kvs) { 46 | switch (kvs) { 47 | case [] -> [] 48 | case (k', v') :: kvs' -> 49 | if (k == k') kvs' 50 | else (k', v') :: remove(k, kvs') 51 | } 52 | } 53 | 54 | sig has : (a, [(a, b)]) ~> Bool 55 | fun has(k, kvs) { 56 | switch (kvs) { 57 | case [] -> false 58 | case (k', _) :: kvs' -> 59 | k == k' || has(k, kvs') 60 | } 61 | } 62 | 63 | ## 64 | ## Basic i/o 65 | ## 66 | typename File = String; 67 | typename FileDescr = Int; 68 | 69 | sig stdout : FileDescr 70 | var stdout = 1; 71 | 72 | sig basicIO : (Comp(a, {Write:(FileDescr,String) -> () |e})) {Write{_} |e}~> (a, File) 73 | fun basicIO(m) { 74 | handle(m()) { 75 | case Return(res) -> (res, "") 76 | case Write(_, s, resume) -> 77 | var (res, file) = resume(()); 78 | (res, s ^^ file) 79 | } 80 | } 81 | 82 | sig echo : (String) {Write:(FileDescr,String) -> () |_}~> () 83 | fun echo(cs) { do Write(stdout,cs) } 84 | 85 | fun example0() { 86 | basicIO(fun() { 87 | echo("Hello"); echo("World") 88 | }) 89 | } 90 | 91 | ## 92 | ## Exceptions: non-local exits 93 | ## 94 | sig exit : (Int) {Exit:(Int) -> Zero |_}-> a 95 | fun exit(n) { switch(do Exit(n)) {} } 96 | 97 | sig status : (Comp(a, {Exit:(Int) -> Zero |e})) {Exit{_} |e}~> Int 98 | fun status(m) { 99 | handle(m()) { 100 | case Return(_) -> 0 101 | case Exit(n, _) -> n 102 | } 103 | } 104 | 105 | fun example1() { 106 | basicIO(fun() { 107 | status(fun() { 108 | echo("dead"); exit(1); echo("code") 109 | }) 110 | }) 111 | } 112 | 113 | ## 114 | ## Dynamic binding: user-specific environments. 115 | ## 116 | typename User = [|Alice|Bob|Root|]; 117 | 118 | sig whoami : () {Ask:String |_}-> String 119 | fun whoami() { do Ask } 120 | 121 | sig env : (User, Comp(a, {Ask:String |e})) {Ask{_} |e}~> a 122 | fun env(user, m) { 123 | handle(m()) { 124 | case Return(x) -> x 125 | case Ask(resume) -> 126 | switch (user) { 127 | case Alice -> resume("alice") 128 | case Bob -> resume("bob") 129 | case Root -> resume("root") 130 | } 131 | } 132 | } 133 | 134 | fun example2() { 135 | env(Root, whoami) 136 | } 137 | 138 | ### 139 | ### Session management. 140 | ### 141 | sig su : (User) {Su:(User) -> () |_}-> () 142 | fun su(user) { do Su(user) } 143 | 144 | sig sessionmgr : (User, Comp(a, {Ask:String,Su:(User) -> () |e})) {Ask{_},Su{_} |e}~> a 145 | fun sessionmgr(user, m) { 146 | env(user, fun() { 147 | handle(m()) { 148 | case Return(x) -> x 149 | case Su(user', resume) -> 150 | env(user', fun() { resume(()) }) 151 | } 152 | }) 153 | } 154 | 155 | fun example3() { 156 | basicIO(fun() { 157 | sessionmgr(Root, fun() { 158 | status(fun() { 159 | su(Alice); echo(whoami()); echo(" "); 160 | su(Bob); echo(whoami()); echo(" "); 161 | su(Root); echo(whoami()) 162 | }) 163 | }) 164 | }) 165 | } 166 | 167 | ## 168 | ## Nondeterminism: time sharing. 169 | ## 170 | sig fork : () {Fork:Bool |_}-> Bool 171 | fun fork() { do Fork } 172 | 173 | sig nondet : (Comp(a, {Fork:Bool |e})) {Fork{_} |e}~> [a] 174 | fun nondet(m) { 175 | handle(m()) { 176 | case Return(res) -> [res] 177 | case Fork(resume) -> resume(true) ++ resume(false) 178 | } 179 | } 180 | 181 | sig interrupt : () {Interrupt:() |_}-> () 182 | fun interrupt() { do Interrupt } 183 | 184 | typename Pstate(a,e::Eff) = forall q::Presence. 185 | [|Done:a 186 | |Paused:() {Interrupt{q} |e}~> Pstate(a, { |e})|]; 187 | 188 | sig slice : (Comp(a, {Interrupt:() |e})) {Interrupt{_} |e}~> Pstate(a, { |e}) 189 | fun slice(m) { 190 | handle(m()) { 191 | case Return(res) -> Done(res) 192 | case Interrupt(resume) -> Paused(fun() { resume(()) }) 193 | } 194 | } 195 | 196 | sig schedule : ([Pstate(a, { Fork:Bool |e})]) {Fork{_},Interrupt{_} |e}~> [a] 197 | fun schedule(ps) { 198 | # sig run : (Pstate (a, { Fork:() {}-> Bool|e })) {Fork{_},Interrupt{_} |e}~> [a] 199 | # fun run(p) { 200 | # switch(p) { 201 | # case Done(res) -> [res] 202 | # case Paused(resume) -> runNext(nondet(resume)) 203 | # } 204 | # } 205 | # concatMap(run, ps) 206 | fun schedule(ps, done) { 207 | switch (ps) { 208 | case [] -> done 209 | case Done(res) :: ps' -> 210 | schedule(ps', res :: done) 211 | case Paused(resume) :: ps' -> 212 | schedule(ps' ++ nondet(resume), done) 213 | } 214 | } 215 | schedule(ps, []) 216 | } 217 | 218 | sig timeshare : (Comp(a, {Fork:Bool,Interrupt:() |e})) {Fork{_},Interrupt{_} |e}~> [a] 219 | fun timeshare(m) { 220 | var p = Paused(fun() { slice(m) }); 221 | schedule([p]) 222 | } 223 | 224 | fun example4() { 225 | basicIO(fun() { 226 | timeshare(fun() { 227 | sessionmgr(Root, fun() { 228 | status(fun() { 229 | var parent = fork(); 230 | (if (parent) su(Alice) else su(Bob)); 231 | echo(whoami() ^^ "> Hello "); 232 | interrupt(); 233 | echo(whoami() ^^ "> Bye "); 234 | interrupt(); 235 | if (parent) exit(0) 236 | else { 237 | var parent = fork(); 238 | interrupt(); 239 | (if (parent) su(Root) 240 | else { 241 | echo(whoami() ^^ "> oops "); 242 | exit(1) 243 | }); 244 | echo(whoami() ^^ "> !! ") 245 | } 246 | }) 247 | }) 248 | }) 249 | }) 250 | } 251 | 252 | fun forktest(n) { 253 | fun loop(i, n) { 254 | if (i >= n) (-1) 255 | else { 256 | var x = fork(); 257 | println("< x = " ^^ (if (x) "true" else "false") ^^ ", i = " ^^ intToString(i)); 258 | ignore(loop(i+1,n)); 259 | println("> x = " ^^ (if (x) "true" else "false") ^^ ", i = " ^^ intToString(i)); 260 | exit(i) 261 | } 262 | } 263 | loop(0, n) 264 | } 265 | 266 | fun test(i) { 267 | if (i == 2) () 268 | else { 269 | println("< i = " ^^ intToString(i)); 270 | var x = fork(); 271 | test(i+1); 272 | println("> i = " ^^ intToString(i)) 273 | } 274 | } 275 | 276 | fun ritchie() { 277 | echo("UNIX is basically "); 278 | echo("a simple operating system, "); 279 | echo("but "); 280 | echo("you have to be a genius to understand the simplicity.\n") 281 | } 282 | 283 | fun hamlet() { 284 | echo("To be, or not to be,\n"); 285 | echo("that is the question:\n"); 286 | echo("Whether 'tis nobler in the mind to suffer\n") 287 | } 288 | 289 | fun example5() { 290 | basicIO(fun() { 291 | nondet(fun() { 292 | sessionmgr(Root, fun() { 293 | status(fun() { 294 | if (fork()) { 295 | su(Alice); 296 | ritchie() 297 | } else { 298 | su(Bob); 299 | hamlet() 300 | } 301 | }) 302 | }) 303 | }) 304 | }) 305 | } 306 | 307 | fun interruptWrite(m) { 308 | handle(m()) { 309 | case Return(res) -> res 310 | case Write(fd, cs, resume) -> 311 | interrupt(); resume(do Write(fd, cs)) 312 | } 313 | } 314 | 315 | fun example5'() { 316 | basicIO(fun() { 317 | timeshare(fun() { 318 | interruptWrite(fun() { 319 | sessionmgr(Root, fun() { 320 | status(fun() { 321 | if (fork()) { 322 | su(Alice); 323 | ritchie() 324 | } else { 325 | su(Bob); 326 | hamlet() 327 | } 328 | }) 329 | }) 330 | }) 331 | }) 332 | }) 333 | } 334 | 335 | ## 336 | ## Generic state handling 337 | ## 338 | sig get : () {Get:s |_}-> s 339 | fun get() { do Get } 340 | 341 | sig put : (s) {Put:(s) -> () |_}-> () 342 | fun put(st) { do Put(st) } 343 | 344 | sig runState : (s, Comp(a, {Get:() -> s,Put:(s) -> () |e})) {Get{_},Put{_} |e}~> (a, s) 345 | fun runState(st0, m) { 346 | var f = handle(m()) { 347 | case Return(x) -> fun(st) { (x, st) } 348 | case Get(resume) -> fun(st) { resume(st)(st) } 349 | case Put(st,resume) -> fun(_) { resume(())(st) } 350 | }; 351 | f(st0) 352 | } 353 | 354 | fun example6() { 355 | runState(0, fun() { 356 | var x = 3; 357 | put(x); 358 | assert(x == get(), "Put;Get"); 359 | var y = get(); 360 | var z = get(); 361 | assert(y == z, "Get;Get"); 362 | put(x+1); 363 | put(x+2); 364 | assert(get() == x + 2, "Put;Put") 365 | }) 366 | } 367 | 368 | ## 369 | ## State: file i/o 370 | ## 371 | typename FilePtr = Option(FileDescr); 372 | 373 | typename INode = (loc:Int,lno:Int); 374 | typename IList = [(Int, INode)]; # INode index -> INode 375 | typename Directory = [(String, Int)]; # Filename -> INode index 376 | typename DataRegion = [(Int, File)]; # Loc -> File 377 | 378 | typename FileSystem = (dir:Directory,dregion:DataRegion,inodes:IList 379 | ,lnext:Int ,inext:Int ); 380 | 381 | sig fsys0 : FileSystem 382 | var fsys0 = (dir = [("stdout", 0)], inodes = [(0, (loc=0, lno=1))], dregion = [(0, "")], lnext = 1, inext = 1); 383 | 384 | sig fopen : (String, FileSystem) {Fail:Zero |_}~> (Int, FileSystem) 385 | fun fopen(fname, fsys) { (lookup(fname, fsys.dir), fsys) } 386 | 387 | sig ftruncate : (Int, FileSystem) {Fail:Zero |_}~> FileSystem 388 | fun ftruncate(ino, fsys) { 389 | var inode = lookup(ino, fsys.inodes); 390 | var dregion = modify(inode.loc, "", fsys.dregion); 391 | (fsys with =dregion) 392 | } 393 | 394 | sig fcreate : (String, FileSystem) {Fail:Zero |_}~> (Int, FileSystem) 395 | fun fcreate(fname, fsys) { 396 | if (has(fname, fsys.dir)) { 397 | var (ino, fsys) = fopen(fname, fsys); 398 | (ino, ftruncate(ino, fsys)) 399 | } else { 400 | var loc = fsys.lnext; 401 | var dregion = (loc, "") :: fsys.dregion; 402 | 403 | var ino = fsys.inext; 404 | var inode = (loc=loc,lno=1); 405 | var inodes = (ino, inode) :: fsys.inodes; 406 | 407 | var dir = (fname, ino) :: fsys.dir; 408 | (ino, (=dir, =dregion, =inodes, lnext=loc+1, inext=ino+1)) 409 | } 410 | } 411 | 412 | sig ftruncate : (Int, FileSystem) {Fail:Zero |_}~> FileSystem 413 | fun ftruncate(ino, fsys) { 414 | var inode = lookup(ino, fsys.inodes); 415 | var dregion = modify(inode.loc, "", fsys.dregion); 416 | (fsys with =dregion) 417 | } 418 | 419 | sig fopen : (String, FileSystem) {Fail:Zero |_}~> Int 420 | fun fopen(fname, fsys) { lookup(fname, fsys.dir) } 421 | 422 | sig fclose : (Int, FileSystem) ~> FileSystem 423 | fun fclose(_, fsys) { fsys } 424 | 425 | sig fwrite : (Int, String, FileSystem) {Fail:Zero |_}~> FileSystem 426 | fun fwrite(ino, cs, fsys) { 427 | var inode = lookup(ino, fsys.inodes); 428 | var file = lookup(inode.loc, fsys.dregion); 429 | var file' = file ^^ cs; 430 | (fsys with dregion = modify(inode.loc, file', fsys.dregion)) 431 | } 432 | 433 | sig fread : (Int, FileSystem) {Fail:Zero |_}~> String 434 | fun fread(ino, fsys) { 435 | var inode = lookup(ino, fsys.inodes); 436 | lookup(inode.loc, fsys.dregion) 437 | } 438 | 439 | sig flink : (String, String, FileSystem) {Fail:Zero |_}~> FileSystem 440 | fun flink(src, dest, fsys) { 441 | var ino = lookup(dest, fsys.dir); 442 | var inode = lookup(ino, fsys.inodes); 443 | var inode' = (inode with lno = inode.lno + 1); 444 | var inodes = modify(ino, inode', fsys.inodes); 445 | 446 | var dir = (src, ino) :: fsys.dir; 447 | (fsys with inodes = inodes, dir = dir) 448 | } 449 | 450 | sig funlink : (String, FileSystem) {Fail:Zero |_}~> FileSystem 451 | fun funlink(fname, fsys) { 452 | var i = lookup(fname, fsys.dir); 453 | var dir = remove(fname, fsys.dir); 454 | 455 | var inode = lookup(i, fsys.inodes); 456 | var inode' = (inode with lno = inode.lno - 1); 457 | 458 | if (inode'.lno > 0) { 459 | var inodes = modify(i, inode', fsys.inodes); 460 | (fsys with inodes = inodes, dir = dir) 461 | } else { 462 | var dregion = remove(inode'.loc, fsys.dregion); 463 | var inodes = remove(i, fsys.inodes); 464 | (fsys with inodes = inodes, dir = dir, dregion = dregion) 465 | } 466 | } 467 | 468 | sig create : (String) {Create:(String) -> FileDescr |_}-> FileDescr 469 | fun create(fname) { do Create(fname) } 470 | 471 | sig truncate : (FileDescr) {Truncate:(FileDescr) -> () |_}-> () 472 | fun truncate(fd) { do Truncate(fd) } 473 | 474 | sig open' : (String) {Open:(String) -> Option(FileDescr) |_}-> Option(FileDescr) 475 | fun open'(fname) { do Open(fname) } 476 | 477 | sig close : (FileDescr) {Close:(FileDescr) -> () |_}-> () 478 | fun close(fd) { do Close(fd) } 479 | 480 | sig write : (FileDescr, String) {Write:(FileDescr, String) -> () |_}-> () 481 | fun write(fd, cs) { do Write(fd, cs) } 482 | 483 | sig read : (FileDescr) {Read:(FileDescr) -> Option(String) |_}-> Option(String) 484 | fun read(fd) { do Read(fd) } 485 | 486 | sig link : (String, String) {Link:(String, String) -> () |_}-> () 487 | fun link(src, dest) { do Link(src, dest) } 488 | 489 | sig unlink : (String) {Unlink:(String) -> () |_}-> () 490 | fun unlink(fname) { do Unlink(fname) } 491 | 492 | sig injectState : (() { |e}~> a) -> () {Get:s,Put:(s) -> () |e}~> a 493 | fun injectState(f) { (f : (() {Get:s,Put:(s) -> () |e}~> a) <- (() { |e}~> a)) } 494 | 495 | sig fileIO : (Comp(a, { #Close:(FileDescr) -> () 496 | Create:(String) -> FileDescr 497 | , Read:(FileDescr) -> Option(String) 498 | , Open:(String) -> Option(FileDescr) 499 | , Truncate:(FileDescr) -> () 500 | , Write:(FileDescr, String) -> () 501 | , Fail{p}|e})) 502 | {Create{_},Read{_},Open{_},Truncate{_},Write{_},Get:FileSystem,Put:(FileSystem) -> (),Fail{p} |e}~> a 503 | fun fileIO(m) { 504 | handle(injectState(m)()) { 505 | case Return(x) -> x 506 | case Create(fname, resume) -> 507 | var ino = withDefault(-1, fun() { 508 | var (ino, fsys) = fcreate(fname, get()); 509 | put(fsys); ino 510 | }); resume(ino) 511 | case Read(ino, resume) -> 512 | var contents = optionalise(fun() { fread(ino, get()) }); 513 | resume(contents) 514 | case Open(fname, resume) -> 515 | var ino = optionalise(fun() { fopen(fname, get()) }); 516 | resume(ino) 517 | case Truncate(ino, resume) -> 518 | withDefault((), fun() { 519 | var fsys = ftruncate(ino, get()); 520 | put(fsys) 521 | }); resume(()) 522 | case Write(ino, cs, resume) -> 523 | withDefault((), fun() { 524 | var fsys = fwrite(ino, cs, get()); 525 | put(fsys) 526 | }); resume(()) 527 | } 528 | } 529 | 530 | fun init(fsys, main) { 531 | runState(fsys, fun() { 532 | fileIO(fun() { 533 | timeshare(fun() { 534 | sessionmgr(Root, fun() { 535 | status(fun() { 536 | if(fork()) exit(0) else main() 537 | }) 538 | }) 539 | }) 540 | }) 541 | }) 542 | } 543 | 544 | ### 545 | ### Stream redirection 546 | ### 547 | sig >> : (Comp(a, { Create: (String) -> FileDescr 548 | , Write:(FileDescr, String) -> () |e}), String) 549 | { Create:(String) -> FileDescr 550 | , Write:(FileDescr, String) -> () |e}~> a 551 | op f >> fname { 552 | var fd = create(fname); 553 | handle(f()) { 554 | case Return(x) -> x 555 | case Write(_, cs, resume) -> 556 | resume(write(fd, cs)) 557 | } 558 | } 559 | 560 | sig >>> : (Comp(a, { Create: (String) -> FileDescr 561 | , Open: (String) -> Option (FileDescr) 562 | , Write:(FileDescr, String) -> () |e}), String) 563 | { Create:(String) -> FileDescr 564 | , Open:(String) -> Option (FileDescr) 565 | , Write:(FileDescr, String) -> () |e}~> () 566 | op f >>> fname { 567 | var fd = switch (open'(fname)) { 568 | case None -> create(fname) 569 | case Some(fd) -> fd 570 | }; 571 | handle(f()) { 572 | case Return(_) -> () 573 | case Write(_, cs, resume) -> 574 | resume(write(fd, cs)) 575 | } 576 | } 577 | 578 | fun example7() { 579 | if (fork()) { 580 | su(Alice); 581 | ritchie >> "ritchie.txt" 582 | } else { 583 | su(Bob); 584 | hamlet >> "hamlet" 585 | } 586 | } 587 | 588 | ### 589 | ### TCP threeway handshake 590 | ### 591 | 592 | sig strsplit : (Char, String) ~> [String] 593 | fun strsplit(c, str) { 594 | fun loop(c, str, i, j) { 595 | if (i >= strlen(str)) [strsub(str, j, i - j)] 596 | else if (charAt(str, i) == c) 597 | strsub(str, j, i - j) :: loop(c, str, i+1, i+1) 598 | else 599 | loop(c,str, i+1, j) 600 | } 601 | loop(c, str, 0, 0) 602 | } 603 | 604 | sig read1 : (FileDescr) {Read:(FileDescr) -> Option(String) |e}-> Option(String) 605 | fun read1(fd) { 606 | switch (read(fd)) { 607 | case Some("") -> None 608 | case x -> x 609 | } 610 | } 611 | 612 | sig truncread : (FileDescr) {Read:(FileDescr) -> Option(String),Truncate:(FileDescr) -> () |_}-> Option(String) 613 | fun truncread(fd) { 614 | var cs = read1(fd); 615 | truncate(fd); cs 616 | } 617 | 618 | sig synced : (Comp(Option(a), {Interrupt:() |e})) {Interrupt:() |e}~> a 619 | fun synced(f) { 620 | switch (f()) { 621 | case None -> interrupt(); synced(f) 622 | case Some(x) -> x 623 | } 624 | } 625 | 626 | sig fail : () {Fail:Zero |_}-> a 627 | fun fail() { switch(do Fail) {} } 628 | 629 | fun tcpclient(seq, inp, out) { 630 | write(out, "SYN " ^^ intToString(seq)); 631 | var resp = synced(fun() { truncread(inp) }); 632 | var [syn, ack] = strsplit(';', resp); 633 | var seqn = stringToInt(strsub(syn, 4, strlen(syn) - 4)); 634 | var ackn = stringToInt(strsub(ack, 4, strlen(ack) - 4)); 635 | if (ackn <> seq + 1) fail() 636 | else write(out, "ACK " ^^ intToString(seqn + 1)) 637 | } 638 | 639 | fun tcpserver(seq, inp, out) { 640 | var req = synced(fun() { truncread(inp) }); 641 | var reqn = stringToInt(strsub(req, 4, strlen(req) - 4)); 642 | var resp = "SYN " ^^ intToString(seq) ^^ ";ACK " ^^ intToString(reqn + 1); 643 | write(out, resp); 644 | var resp' = synced(fun() { truncread(inp) }); 645 | var ackn = stringToInt(strsub(resp', 4, strlen(resp') - 4)); 646 | if (ackn <> seq + 1) fail() 647 | else () 648 | } 649 | 650 | fun performTCP(tcpf, seq, inp, out) { 651 | var fd = create(whoami() ^^ ".log"); 652 | handle(tcpf(seq, inp, out)) { 653 | case Return(_) -> write(fd, "Handshake completed.") 654 | case Fail(_) -> write(fd, "Handshake failed.") 655 | } 656 | } 657 | 658 | fun tcphandshake() { 659 | var (cfd, sfd) = (create("client.sock"), create("server.sock")); 660 | if (fork()) { 661 | su(Alice); 662 | performTCP(tcpclient, 42, cfd, sfd) 663 | } else { 664 | su(Bob); 665 | performTCP(tcpserver, 84, sfd, cfd) 666 | } 667 | } 668 | 669 | fun tcphandshakeFail() { 670 | var (cfd, sfd) = (create("client.sock"), create("server.sock")); 671 | if (fork()) { 672 | su(Alice); 673 | handle(performTCP(tcpclient, 42, cfd, sfd)) { 674 | case Write(fd, cs, resume) -> 675 | resume(if (strsub(cs, 0, 3) == "ACK") write(fd, "ACK 0") 676 | else write(fd, cs)) 677 | } 678 | } else { 679 | su(Bob); 680 | performTCP(tcpserver, 84, sfd, cfd) 681 | } 682 | } 683 | 684 | # 685 | # Grep 686 | # 687 | #sig grep : (String) {Await:Char,Yield:(Char) -> () |_}~> () 688 | fun grep(str) { 689 | var cs = explode(str); 690 | fun match(c,cs) { 691 | switch (cs) { 692 | case c' :: cs' -> 693 | if (c == '\n') fail() 694 | if (c == c') 695 | } 696 | } 697 | } 698 | -------------------------------------------------------------------------------- /macros.tex: -------------------------------------------------------------------------------- 1 | %% 2 | %% Defined-as equality 3 | %% 4 | \newcommand{\defas}[0]{\mathrel{\overset{\makebox[0pt]{\mbox{\normalfont\tiny\text{def}}}}{=}}} 5 | \newcommand{\defnas}[0]{\mathrel{:=}} 6 | \newcommand{\simdefas}[0]{\mathrel{\overset{\makebox[0pt]{\mbox{\normalfont\tiny\text{def}}}}{\simeq}}} 7 | \newcommand{\adef}[0]{\mathrel{\overset{\makebox[0pt]{\mbox{\normalfont\tiny{\text{$\alpha$-def}}}}}{\simeq}}} 8 | 9 | %% 10 | %% Some useful maths abbreviations 11 | %% 12 | \newcommand{\C}{\ensuremath{\mathbb{C}}} 13 | \newcommand{\N}{\ensuremath{\mathbb{N}}} 14 | \newcommand{\R}{\ensuremath{\mathbb{R}}} 15 | \newcommand{\Z}{\ensuremath{\mathbb{Z}}} 16 | \newcommand{\B}{\ensuremath{\mathbb{B}}} 17 | \newcommand{\BB}[1]{\ensuremath{\mathbf{#1}}} 18 | \newcommand{\CC}{\keyw{ctrl}} 19 | % \newcommand{\Delim}[1]{\ensuremath{\langle\!\!\mkern-1.5mu\langle#1\rangle\!\!\mkern-1.5mu\rangle}} 20 | \newcommand{\Delim}[1]{\ensuremath{\keyw{del}.#1}} 21 | \newcommand{\sembr}[1]{\ensuremath{\llbracket #1 \rrbracket}} 22 | \newcommand{\BigO}{\ensuremath{\mathcal{O}}} 23 | \newcommand{\SC}{\ensuremath{\mathsf{S}}} 24 | \newcommand{\ST}{\ensuremath{\mathsf{T}}} 25 | \newcommand{\ar}{\ensuremath{\mathsf{ar}}} 26 | \newcommand{\Tm}{\ensuremath{\mathsf{Tm}}} 27 | \newcommand{\Ty}{\ensuremath{\mathsf{Ty}}} 28 | 29 | %% 30 | %% Partiality 31 | %% 32 | \newcommand{\pto}[0]{\ensuremath{\rightharpoonup}} 33 | 34 | %% 35 | %% Operation arrows 36 | %% 37 | \newcommand{\opto}[0]{\ensuremath{\twoheadrightarrow}} 38 | 39 | %% 40 | %% Calculi names. 41 | %% 42 | \newcommand{\Links}{Links\xspace} 43 | \newcommand{\CoreLinks}{\ensuremath{\mathsf{CoreLinks}}\xspace} 44 | \newcommand{\BCalc}{\ensuremath{\lambda_{\mathsf{b}}}\xspace} 45 | \newcommand{\BCalcRec}{\ensuremath{\lambda_{\mathsf{b}+\mathsf{rec}}}\xspace} 46 | \newcommand{\HCalc}{\ensuremath{\lambda_{\mathsf{h}}}\xspace} 47 | \newcommand{\SCalc}{\ensuremath{\lambda_{\mathsf{h^\dagger}}}\xspace} 48 | \newcommand{\HSCalc}{\ensuremath{\lambda_{\mathsf{h^\delta}}}\xspace} 49 | \newcommand{\EffCalc}{\ensuremath{\lambda_{\mathsf{eff}}}\xspace} 50 | \newcommand{\UCalc}{\ensuremath{\lambda_{\mathsf{u}}}\xspace} 51 | \newcommand{\param}{\ensuremath{\ddagger}} 52 | \newcommand{\HPCalc}{\ensuremath{\lambda_{\mathsf{h^\param}}}\xspace} 53 | \newcommand{\HPCF}{\ensuremath{\lambda^\rightarrow_{\mathsf{h}}}\xspace} 54 | \newcommand{\BPCF}{\ensuremath{\lambda^\rightarrow_{\mathsf{b}}}\xspace} 55 | 56 | %% 57 | %% Calculi terms and types type-setting. 58 | %% 59 | \newcommand{\revto}{\ensuremath{\leftarrow}} 60 | 61 | \newcommand{\dec}[1]{\mathsf{#1}} 62 | \newcommand{\keyw}[1]{\mathbf{#1}} 63 | \newcommand{\Handle}{\keyw{handle}} 64 | \newcommand{\ParamHandle}{\Handle^\param} 65 | \newcommand{\ShallowHandle}{\ensuremath{\keyw{handle}^\dagger}} 66 | \newcommand{\With}{\keyw{with}} 67 | \newcommand{\Let}{\keyw{let}} 68 | \newcommand{\Rec}{\keyw{rec}} 69 | \newcommand{\In}{\keyw{in}} 70 | \newcommand{\Do}{\keyw{do}} 71 | \newcommand{\Return}{\keyw{return}} 72 | \newcommand{\Val}{\keyw{val}} 73 | \newcommand{\Case}{\keyw{case}} 74 | \newcommand{\If}{\keyw{if}} 75 | \newcommand{\Then}{\keyw{then}} 76 | \newcommand{\Else}{\keyw{else}} 77 | \newcommand{\Absurd}{\keyw{absurd}} 78 | \newcommand{\Record}[1]{\ensuremath{\langle #1 \rangle}} 79 | \newcommand{\Op}[1]{\ensuremath{\langle\!\!\langle #1 \rangle\!\!\rangle}} 80 | %\newcommand{\Op}[1]{\ensuremath{\{#1\}}} 81 | \newcommand{\OpCase}[3]{\Op{#1~#2 \opto #3}} 82 | \newcommand{\ExnCase}[2]{\Op{#1~#2}} 83 | \newcommand{\Unit}{\Record{}} 84 | \newcommand{\Inl}{\keyw{inl}} 85 | \newcommand{\Inr}{\keyw{inr}} 86 | \newcommand{\Thunk}{\lambda \Unit.} 87 | \newcommand{\PCFRef}{\dec{Ref}} 88 | \newcommand{\refv}{\keyw{ref}} 89 | 90 | \newcommand{\Pre}[1]{\mathsf{Pre}(#1)} 91 | \newcommand{\Abs}{\mathsf{Abs}} 92 | \newcommand{\Presence}{\mathsf{Presence}} 93 | \newcommand{\Row}{\mathsf{Row}} 94 | \newcommand{\Type}{\mathsf{Type}} 95 | \newcommand{\Ground}{\mathsf{ground}} 96 | 97 | \newcommand{\Comp}{\mathsf{Comp}} 98 | \newcommand{\Effect}{\mathsf{Effect}} 99 | \newcommand{\Handler}{\mathsf{Handler}} 100 | 101 | \newcommand{\ZeroType}{0} 102 | \newcommand{\UnitType}{1} 103 | \newcommand{\One}{1} 104 | \newcommand{\Int}{\mathsf{Int}} 105 | \newcommand{\Float}{\mathsf{Float}} 106 | \newcommand{\Bool}{\mathsf{Bool}} 107 | \newcommand{\List}{\mathsf{List}} 108 | \newcommand{\Nat}{\mathsf{Nat}} 109 | \newcommand{\Choose}{\dec{Choose}} 110 | \newcommand{\Count}{\dec{count}} 111 | \newcommand{\GenericSearch}{\dec{genericSearch}} 112 | \newcommand{\Predicate}{\dec{Predicate}} 113 | \newcommand{\Point}{\dec{Point}} 114 | \newcommand{\Branch}{\dec{Branch}} 115 | \newcommand{\Get}{\dec{Get}} 116 | \newcommand{\Put}{\dec{Put}} 117 | \newcommand{\Zero}{\dec{Zero}} 118 | \newcommand{\Fail}{\dec{Fail}} 119 | \newcommand{\Read}{\dec{Read}} 120 | \newcommand{\Write}{\dec{Write}} 121 | \newcommand{\Char}{\dec{Char}} 122 | \newcommand{\String}{\dec{String}} 123 | 124 | \newcommand{\True}{\mathsf{true}} 125 | \newcommand{\False}{\mathsf{false}} 126 | 127 | \newcommand{\eff}{!} 128 | \newcommand{\typ}[2]{#1 \vdash #2} 129 | \newcommand{\typv}[2]{#1 \vdash #2} 130 | \newcommand{\typc}[3]{#1 \vdash #2 \eff #3} 131 | \newcommand{\Harrow}{\Rightarrow} 132 | 133 | \newcommand{\FTV}{\ensuremath{\mathrm{FTV}}} 134 | \newcommand{\FV}{\ensuremath{\mathrm{FV}}} 135 | 136 | \newcommand{\reducesto}[0]{\ensuremath{\leadsto}} 137 | \newcommand{\areducesto}{\ensuremath{\reducesto_{\textrm{a}}}} 138 | \newcommand{\stepsto}[0]{\ensuremath{\longrightarrow}} 139 | \newcommand{\Stepsto}{\Longrightarrow} 140 | \newcommand{\EC}{\ensuremath{\mathcal{E}}} 141 | 142 | \newcommand{\BL}{\ensuremath{\mathsf{BL}}} 143 | 144 | \newcommand{\dom}{\ensuremath{\mathsf{dom}}} 145 | 146 | \newcommand{\Res}{\keyw{res}} 147 | 148 | \newcommand{\Cong}{\mathrm{cong}} 149 | 150 | \newcommand{\AlgTheory}{\ensuremath{\mathcal{T}}} 151 | 152 | %% Handler projections. 153 | \newcommand{\mret}{\mathrm{ret}} 154 | \newcommand{\mops}{\mathrm{ops}} 155 | \newcommand{\hret}{H^{\mret}} 156 | \newcommand{\hval}{\hret} 157 | \newcommand{\hops}{H^{\mops}} 158 | %\newcommand{\hex}{H^{\mathrm{ex}}} 159 | \newcommand{\hell}{H^{\ell}} 160 | \newcommand{\gell}{\theta^{\ell}} 161 | 162 | \newcommand{\depth}{\delta} 163 | 164 | \newcommand{\alertbox}[2]{{\par\noindent\small\color{red} \framebox{\parbox{\dimexpr\linewidth-2\fboxsep-2\fboxrule}{\textbf{#1:} #2}}}} 165 | \newcommand{\todo}[1]{\alertbox{TODO}{#1}} 166 | \newcommand{\dhil}[1]{\alertbox{Daniel}{#1}} 167 | 168 | %% 169 | %% Labels 170 | %% 171 | \newcommand{\slab}[1]{\ensuremath{\mathsf{#1}}} 172 | \newcommand{\rulelabel}[2]{\ensuremath{\mathsf{#1\textrm{-}#2}}} 173 | \newcommand{\klab}[1]{\rulelabel{K}{#1}} 174 | \newcommand{\semlab}[1]{\rulelabel{S}{#1}} 175 | \newcommand{\usemlab}[1]{\rulelabel{U}{#1}} 176 | \newcommand{\tylab}[1]{\rulelabel{T}{#1}} 177 | \newcommand{\mlab}[1]{\rulelabel{M}{#1}} 178 | \newcommand{\siglab}[1]{\rulelabel{Sig}{#1}} 179 | \newcommand{\rowlab}[1]{\rulelabel{R}{#1}} 180 | 181 | %% 182 | %% Syntactic categories. 183 | %% 184 | \newcommand{\CatName}[1]{\textrm{#1}} 185 | \newcommand{\CompCat}{\CatName{Comp}} 186 | \newcommand{\UCompCat}{\CatName{UComp}} 187 | \newcommand{\UValCat}{\CatName{UVal}} 188 | \newcommand{\SCompCat}{\CatName{SComp}} 189 | \newcommand{\SValCat}{\CatName{SVal}} 190 | \newcommand{\SPatCat}{\CatName{SPat}} 191 | \newcommand{\ValCat}{\CatName{Val}} 192 | \newcommand{\VarCat}{\CatName{Var}} 193 | \newcommand{\ValTypeCat}{\CatName{VType}} 194 | \newcommand{\CompTypeCat}{\CatName{CType}} 195 | \newcommand{\HandlerTypeCat}{\CatName{HType}} 196 | \newcommand{\PresenceCat}{\CatName{Presence}} 197 | \newcommand{\TypeCat}{\CatName{Type}} 198 | \newcommand{\TyVarCat}{\CatName{TVar}} 199 | \newcommand{\KindCat}{\CatName{Kind}} 200 | \newcommand{\RowCat}{\CatName{Row}} 201 | \newcommand{\EffectCat}{\CatName{Effect}} 202 | \newcommand{\TermCat}{\CatName{Term}} 203 | \newcommand{\LabelCat}{\CatName{Label}} 204 | \newcommand{\TyEnvCat}{\CatName{TyEnv}} 205 | \newcommand{\KindEnvCat}{\CatName{KindEnv}} 206 | \newcommand{\EvalCat}{\CatName{Cont}} 207 | \newcommand{\UEvalCat}{\CatName{UCont}} 208 | \newcommand{\HandlerCat}{\CatName{HDef}} 209 | \newcommand{\MConfCat}{\CatName{Conf}} 210 | \newcommand{\MEnvCat}{\CatName{Env}} 211 | \newcommand{\MValCat}{\CatName{Mval}} 212 | \newcommand{\MGContCat}{\CatName{GenCont}} 213 | \newcommand{\MGFrameCat}{\CatName{GenFrame}} 214 | \newcommand{\MPContCat}{\CatName{PureCont}} 215 | \newcommand{\MPFrameCat}{\CatName{PureFrame}} 216 | \newcommand{\MHCloCat}{\CatName{HClo}} 217 | 218 | %% 219 | %% Lindley's array stuff. 220 | %% 221 | \newcommand{\ba}{\begin{array}} 222 | \newcommand{\ea}{\end{array}} 223 | 224 | \newcommand{\bl}{\ba[t]{@{}l@{}}} 225 | \newcommand{\el}{\ea} 226 | 227 | 228 | %% 229 | %% Lindley's syntax, reductions, equations, and derivation environments. 230 | %% 231 | \newenvironment{syntax}{\[\ba{@{}l@{\quad}r@{~}c@{~}l@{}}}{\ea\]\ignorespacesafterend} 232 | \newenvironment{reductions}{\[\ba{@{}l@{\qquad}@{}r@{~~}c@{~~}l@{}}}{\ea\]\ignorespacesafterend} 233 | 234 | \newenvironment{eqs}{\ba{@{}r@{~}c@{~}l@{}}}{\ea} 235 | \newenvironment{equations}{\[\ba{@{}r@{~}c@{~}l@{}}}{\ea\]\ignorespacesafterend} 236 | \newcommand\numberthis{\addtocounter{equation}{1}\tag{$\ast$\theequation}} % Numbering equations 237 | \newenvironment{derivation}{\begin{displaymath}\ba{@{}r@{~}l@{}}}{\ea\end{displaymath}\ignorespacesafterend} 238 | \newcommand{\reason}[1]{\quad (\text{#1})} 239 | 240 | 241 | \newenvironment{smathpar}{\vspace{-3ex}\small\begin{mathpar}}{\end{mathpar}\normalsize\ignorespacesafterend} 242 | 243 | %% 244 | %% Lists 245 | %% 246 | \newcommand{\nil}{\ensuremath{[]}} 247 | \newcommand{\cons}{\ensuremath{::}} 248 | 249 | \newcommand{\concat}{\mathbin{+\!\!+}} 250 | \newcommand{\revconcat}{\mathbin{\widehat{\concat}}} 251 | \newcommand{\snoc}[2]{\ensuremath{#1 \concat [#2]}} 252 | 253 | %% 254 | %% CPS notation 255 | %% 256 | % static / dynamic stuff 257 | \newcommand{\scol}[1]{{\color{blue}#1}} 258 | \newcommand{\dcol}[1]{{\color{red}#1}} 259 | 260 | \newcommand{\static}[1]{\scol{\overline{#1}}} 261 | \newcommand{\dynamic}[1]{\dcol{\underline{#1}}} 262 | \newcommand{\nary}[1]{\overline{#1}} 263 | 264 | \newcommand{\slam}{\static{\lambda}} 265 | \newcommand{\dlam}{\dynamic{\lambda}} 266 | \newcommand{\sapp}{\mathbin{\static{@}}} 267 | \newcommand{\dapp}{\mathbin{\dynamic{@}}} 268 | 269 | \newcommand{\reify}{\mathord{\downarrow}} 270 | \newcommand{\reflect}{\mathord{\uparrow}} 271 | 272 | \newcommand{\scons}{\mathbin{\static{\cons}}} 273 | \newcommand{\dcons}{\mathbin{\dynamic{\cons}}} 274 | 275 | \newcommand{\snil}{\static{\nil}} 276 | \newcommand{\dnil}{\dynamic{\nil}} 277 | 278 | \newcommand{\sRecord}[1]{\static{\langle}#1\static{\rangle}} 279 | \newcommand{\dRecord}[1]{\dynamic{\langle}#1\dynamic{\rangle}} 280 | 281 | \newcommand{\sQ}{\mathcal{Q}} 282 | \newcommand{\sV}{\mathcal{V}} 283 | \newcommand{\sW}{\mathcal{W}} 284 | 285 | \newcommand{\sM}{\mathcal{M}} 286 | \renewcommand{\snil}{\reflect \dnil} 287 | 288 | \newcommand{\cps}[1]{\ensuremath{\llbracket #1 \rrbracket}} 289 | \newcommand{\pcps}[1]{\top\cps{#1}} 290 | 291 | \newcommand{\hforward}{M_{\textrm{forward}}} 292 | \newcommand{\hid}{V_{\textrm{id}}} 293 | 294 | % continuation application 295 | \newcommand{\kapp}{\keyw{app}} 296 | 297 | %%% Continuation names 298 | %%% The following set of macros are a bit more consistent with those 299 | %%% currently used by the abstract machine, and don't use the plural 300 | %%% convention of functional programming. 301 | 302 | % dynamic 303 | \newcommand{\dlf}{f} % let frames 304 | \newcommand{\dlk}{fs} % let continuations 305 | \newcommand{\dhf}{q} % handler frames 306 | \newcommand{\dhk}{ks} % handler continuations 307 | \newcommand{\dhkr}{rs} % reverse handler continuations 308 | \newcommand{\dLet}{\dynamic{\Let}} 309 | \newcommand{\dIn}{\dynamic{\In}} 310 | 311 | % static 312 | \newcommand{\slf}{\phi} % let frames 313 | \newcommand{\slk}{\sigma} % let continuations 314 | \newcommand{\shf}{\theta} % handler frames 315 | \newcommand{\shk}{\kappa} % handler continuations 316 | \newcommand{\sLet}{\static{\Let}} 317 | \newcommand{\sIn}{\static{\In}} 318 | % \newcommand{\sk}{\kappa} 319 | % \newcommand{\sks}{\mathit{\kappa s}} 320 | \newcommand{\sks}{\kappa} 321 | % \newcommand{\sh}{\eta} 322 | \newcommand{\sk}{\theta} 323 | \newcommand{\sh}{\chi} 324 | \newcommand{\sx}{\varepsilon} 325 | 326 | \newcommand{\sP}{\mathcal{P}} 327 | \newcommand{\VS}{VS} 328 | \newcommand{\Vmap}{\keyw{vmap}} 329 | \newcommand{\Vmapsnd}{\keyw{vmapsnd}} 330 | \newcommand{\Fun}{\keyw{fun}} 331 | 332 | % Canonical variables for handler components 333 | \newcommand{\vhret}{h^{\mret}} 334 | \newcommand{\vhops}{h^{\mops}} 335 | \newcommand{\sv}{\chi} 336 | \newcommand{\svhret}{\sv^{\mret}} 337 | \newcommand{\svhops}{\sv^{\mops}} 338 | 339 | % \newcommand{\dk}{\dRecord{fs,\dRecord{\vhret,\vhops}}} 340 | \newcommand{\dk}{k} 341 | 342 | % 343 | \renewcommand{\hid}{V_{\mops}} 344 | \newcommand{\kid}{V_\mathrm{id}} 345 | \newcommand{\rid}{V_{\mret}} 346 | 347 | % Examples 348 | \newcommand{\Pipe}{\dec{pipe}} 349 | \newcommand{\Copipe}{\dec{copipe}} 350 | \newcommand{\Ones}{\dec{ones}} 351 | \newcommand{\Yield}{\dec{Yield}} 352 | \newcommand{\Await}{\dec{Await}} 353 | \newcommand{\AddTwo}{\ensuremath{\dec{add}_2}} 354 | \newcommand{\Option}{\dec{Option}} 355 | \newcommand{\Some}{\dec{Some}} 356 | \newcommand{\None}{\dec{None}} 357 | \newcommand{\Toss}{\dec{Toss}} 358 | \newcommand{\toss}{\dec{toss}} 359 | \newcommand{\Heads}{\dec{Heads}} 360 | \newcommand{\Tails}{\dec{Tails}} 361 | \newcommand{\Exn}{\dec{Exn}} 362 | \newcommand{\fail}{\dec{fail}} 363 | \newcommand{\optionalise}{\dec{optionalise}} 364 | \newcommand{\bind}{\ensuremath{\gg\!=}} 365 | \newcommand{\return}{\dec{Return}} 366 | \newcommand{\faild}{\dec{withDefault}} 367 | \newcommand{\Free}{\dec{Free}} 368 | \newcommand{\FreeState}{\dec{FreeState}} 369 | \newcommand{\OpF}{\dec{Op}} 370 | \newcommand{\DoF}{\dec{do}} 371 | \newcommand{\getF}{\dec{get}} 372 | \newcommand{\putF}{\dec{put}} 373 | \newcommand{\fmap}{\dec{fmap}} 374 | \newcommand{\toggle}{\dec{toggle}} 375 | \newcommand{\incrEven}{\dec{incrEven}} 376 | \newcommand{\even}{\dec{even}} 377 | 378 | % Abstract machine 379 | \newcommand{\cek}[1]{\ensuremath{\langle #1 \rangle}} 380 | % Environments 381 | \newcommand{\env}{\ensuremath{\gamma}} 382 | % restrict an environment 383 | \newcommand{\res}{\backslash} 384 | 385 | % abstract machine translations 386 | \newcommand{\val}[2]{\llbracket #1 \rrbracket #2} 387 | \newcommand{\inv}[1]{\llparenthesis #1 \rrparenthesis} 388 | 389 | % configurations 390 | \newcommand{\conf}{\mathcal{C}} 391 | 392 | % effect sugar 393 | \newcommand{\inward}[1]{\mathcal{I}\sembr{#1}} 394 | \newcommand{\outward}[1]{\mathcal{O}\sembr{#1}} 395 | \newcommand{\xcomp}[1]{\outward{#1}} 396 | \newcommand{\xval}[1]{\outward{#1}} 397 | \newcommand{\xpre}[1]{\outward{#1}} 398 | \newcommand{\xrow}[1]{\outward{#1}} 399 | \newcommand{\xeff}[1]{\outward{#1}} 400 | \newcommand{\pcomp}[1]{\inward{#1}} 401 | \newcommand{\pval}[1]{\inward{#1}} 402 | \newcommand{\ppre}[1]{\inward{#1}} 403 | \newcommand{\prow}[1]{\inward{#1}} 404 | \newcommand{\peff}[1]{\inward{#1}} 405 | \newcommand{\eamb}{\ensuremath{E_{\mathsf{amb}}}} 406 | \newcommand{\trval}[1]{\mathcal{T}\sembr{#1}} 407 | 408 | % UNIX example 409 | \newcommand{\UNIX}{UNIX} 410 | \newcommand{\OSname}[0]{Tiny UNIX} 411 | \newcommand{\exit}{\dec{exit}} 412 | \newcommand{\Exit}{\dec{Exit}} 413 | \newcommand{\Status}{\dec{Status}} 414 | \newcommand{\status}{\dec{status}} 415 | \newcommand{\basicIO}{\dec{basicIO}} 416 | \newcommand{\Putc}{\dec{Putc}} 417 | \newcommand{\putc}{\dec{putc}} 418 | \newcommand{\UFile}{\dec{File}} 419 | \newcommand{\UFD}{\dec{FileDescr}} 420 | \newcommand{\fwrite}{\dec{fwrite}} 421 | \newcommand{\iter}{\dec{iter}} 422 | \newcommand{\map}{\dec{map}} 423 | \newcommand{\stdout}{\dec{stdout}} 424 | \newcommand{\IO}{\dec{IO}} 425 | \newcommand{\BIO}{\dec{BIO}} 426 | \newcommand{\Alice}{\dec{Alice}} 427 | \newcommand{\Bob}{\dec{Bob}} 428 | \newcommand{\Root}{\dec{Root}} 429 | \newcommand{\User}{\dec{User}} 430 | \newcommand{\environment}{\dec{env}} 431 | \newcommand{\EnvE}{\dec{Session}} 432 | \newcommand{\Ask}{\dec{Ask}} 433 | \newcommand{\whoami}{\dec{whoami}} 434 | \newcommand{\Su}{\dec{Su}} 435 | \newcommand{\su}{\dec{su}} 436 | \newcommand{\sessionmgr}{\dec{sessionmgr}} 437 | \newcommand{\echo}{\dec{echo}} 438 | \newcommand{\strlit}[1]{\texttt{"#1"}} 439 | \newcommand{\nondet}{\dec{nondet}} 440 | \newcommand{\Fork}{\dec{Fork}} 441 | \newcommand{\fork}{\dec{fork}} 442 | \newcommand{\Interrupt}{\dec{Interrupt}} 443 | \newcommand{\interrupt}{\dec{interrupt}} 444 | \newcommand{\Pstate}{\dec{Pstate}} 445 | \newcommand{\Done}{\dec{Done}} 446 | \newcommand{\Suspended}{\dec{Paused}} 447 | \newcommand{\slice}{\dec{slice}} 448 | \newcommand{\reifyP}{\dec{reifyProcess}} 449 | \newcommand{\timeshare}{\dec{timeshare}} 450 | \newcommand{\runNext}{\dec{runNext}} 451 | \newcommand{\concatMap}{\dec{concatMap}} 452 | \newcommand{\State}{\dec{State}} 453 | \newcommand{\runState}{\dec{runState}} 454 | \newcommand{\Uget}{\dec{get}} 455 | \newcommand{\Uput}{\dec{put}} 456 | \newcommand{\nl}{\textbackslash{}n} 457 | \newcommand{\quoteRitchie}{\dec{ritchie}} 458 | \newcommand{\quoteHamlet}{\dec{hamlet}} 459 | \newcommand{\Proc}{\dec{Proc}} 460 | \newcommand{\schedule}{\dec{schedule}} 461 | \newcommand{\fsname}{BSFS} 462 | \newcommand{\FileSystem}{\dec{FileSystem}} 463 | \newcommand{\Directory}{\dec{Directory}} 464 | \newcommand{\DataRegion}{\dec{DataRegion}} 465 | \newcommand{\INode}{\dec{INode}} 466 | \newcommand{\IList}{\dec{IList}} 467 | \newcommand{\fileRW}{\dec{fileRW}} 468 | \newcommand{\fileAlloc}{\dec{fileCO}} 469 | \newcommand{\URead}{\dec{Read}} 470 | \newcommand{\UWrite}{\dec{Write}} 471 | \newcommand{\UCreate}{\dec{Create}} 472 | \newcommand{\UOpen}{\dec{Open}} 473 | \newcommand{\fread}{\dec{fread}} 474 | \newcommand{\fcreate}{\dec{fcreate}} 475 | \newcommand{\Ucreate}{\dec{create}} 476 | \newcommand{\redirect}{\texttt{>}} 477 | \newcommand{\fopen}{\dec{fopen}} 478 | \newcommand{\lookup}{\dec{lookup}} 479 | \newcommand{\modify}{\dec{modify}} 480 | \newcommand{\fileIO}{\dec{fileIO}} 481 | \newcommand{\ULink}{\dec{Link}} 482 | \newcommand{\UUnlink}{\dec{Unlink}} 483 | \newcommand{\flink}{\dec{flink}} 484 | \newcommand{\funlink}{\dec{funlink}} 485 | \newcommand{\remove}{\dec{remove}} 486 | \newcommand{\FileLU}{\dec{FileLU}} 487 | \newcommand{\fileLU}{\dec{fileLU}} 488 | \newcommand{\FileIO}{\dec{FileIO}} 489 | \newcommand{\FileRW}{\dec{FileRW}} 490 | \newcommand{\FileCO}{\dec{FileCO}} 491 | \newcommand{\cat}{\dec{cat}} 492 | \newcommand{\head}{\dec{head}} 493 | \newcommand{\grep}{\dec{grep}} 494 | \newcommand{\match}{\dec{match}} 495 | \newcommand{\wc}{\dec{wc}} 496 | \newcommand{\forever}{\dec{forever}} 497 | \newcommand{\textnil}{\textbackslash{}0} 498 | \newcommand{\charlit}[1]{\texttt{'#1'}} 499 | \newcommand{\where}{\keyw{where}} 500 | \newcommand{\intToString}{\dec{intToString}} 501 | \newcommand{\freq}{\dec{freq}} 502 | \newcommand{\paste}{\dec{paste}} 503 | \newcommand{\sed}{\dec{sed}} 504 | \newcommand{\printTable}{\dec{renderTable}} 505 | \newcommand{\timesharee}{\dec{timeshare2}} 506 | \newcommand{\Co}{\dec{Co}} 507 | \newcommand{\UFork}{\dec{UFork}} 508 | \newcommand{\ufork}{\dec{ufork}} 509 | \newcommand{\Wait}{\dec{Wait}} 510 | \newcommand{\scheduler}{\dec{scheduler}} 511 | \newcommand{\Sstate}{\dec{Sstate}} 512 | \newcommand{\Ready}{\dec{Ready}} 513 | \newcommand{\Blocked}{\dec{Blocked}} 514 | \newcommand{\init}{\dec{init}} 515 | \newcommand{\Reader}{\dec{Reader}} 516 | \newcommand{\Other}{\dec{Other}} 517 | 518 | %% 519 | %% Some control operators 520 | %% 521 | \newcommand{\Cupto}{\keyw{cupto}} 522 | \newcommand{\Set}{\keyw{set}} 523 | \newcommand{\newPrompt}{\keyw{newPrompt}} 524 | \newcommand{\Callcc}{\keyw{callcc}} 525 | \newcommand{\Callcomc}{\ensuremath{\keyw{callcomp}}} 526 | \newcommand{\textCallcomc}{callcomp} 527 | \newcommand{\Throw}{\keyw{throw}} 528 | \newcommand{\Continue}{\keyw{resume}} 529 | \newcommand{\Catch}{\keyw{catch}} 530 | \newcommand{\Catchcont}{\keyw{catchcont}} 531 | \newcommand{\Control}{\keyw{control}} 532 | \newcommand{\Prompt}{\#} 533 | \newcommand{\Controlz}{\ensuremath{\keyw{control_0}}} 534 | \newcommand{\Spawn}{\keyw{spawn}} 535 | \newcommand{\Promptz}{\ensuremath{\#_0}} 536 | \newcommand{\Escape}{\keyw{escape}} 537 | \newcommand{\shift}{\keyw{shift}} 538 | \newcommand{\shiftz}{\ensuremath{\keyw{shift_0}}} 539 | \newcommand{\CCpp}{\ensuremath{+\CC+}} 540 | \newcommand{\CCpm}{\ensuremath{+\CC-}} 541 | \newcommand{\CCmp}{\ensuremath{-\CC+}} 542 | \newcommand{\CCmm}{\ensuremath{-\CC-}} 543 | \def\sigh#1{% 544 | \pmb{\left\langle\vphantom{#1}\right.}% 545 | #1% 546 | \pmb{\left.\vphantom{#1}\right\rangle}} 547 | \newcommand{\llambda}{\ensuremath{\pmb{\lambda}}} 548 | \newcommand{\reset}[1]{\pmb{\langle} #1 \pmb{\rangle}} 549 | \newcommand{\resetz}[1]{\pmb{\langle} #1 \pmb{\rangle}_0} 550 | \newcommand{\dollarz}[2]{\ensuremath{\reset{#2 \mid #1}}} 551 | \newcommand{\dollarzh}[2]{\ensuremath{#1\,\$_0#2}} 552 | \newcommand{\fcontrol}{\keyw{fcontrol}} 553 | \newcommand{\fprompt}{\%} 554 | \newcommand{\splitter}{\keyw{splitter}} 555 | \newcommand{\abort}{\keyw{abort}} 556 | \newcommand{\calldc}{\keyw{calldc}} 557 | \newcommand{\J}{\keyw{J}} 558 | \newcommand{\JI}{\keyw{J}\,\keyw{I}} 559 | \newcommand{\FelleisenC}{\ensuremath{\keyw{C}}} 560 | \newcommand{\FelleisenF}{\ensuremath{\keyw{F}}} 561 | \newcommand{\cont}{\keyw{cont}} 562 | \newcommand{\Cont}{\dec{Cont}} 563 | \newcommand{\Algol}{Algol~60} 564 | \newcommand{\qq}[1]{\ensuremath{\ulcorner #1 \urcorner}} 565 | \newcommand{\prompttype}{\dec{Prompt}} 566 | \newcommand{\async}{\keyw{async}} 567 | \newcommand{\await}{\keyw{await}} 568 | 569 | % Language macros 570 | \newcommand{\Frank}{Frank} 571 | \newcommand{\SML}{SML} 572 | \newcommand{\SMLNJ}{\SML{}/NJ} 573 | \newcommand{\OCaml}{OCaml} 574 | 575 | 576 | %% 577 | %% Asymptotic improvement macros 578 | %% 579 | \newcommand{\LLL}{\ensuremath{\mathcal L}} 580 | \newcommand{\naive}{naïve\xspace} 581 | \newcommand{\naively}{naïvely\xspace} 582 | \newcommand{\Naive}{Naïve\xspace} 583 | \newcommand{\sem}[1]{\ensuremath{\pi_{#1}}} 584 | \newcommand{\Iff}{\Leftrightarrow} 585 | \newcommand{\Implies}{\Rightarrow} 586 | \newcommand{\BCalcS}{\ensuremath{\lambda_{\textrm{\normalfont s}}\xspace}} 587 | \newcommand{\BCalcE}{\ensuremath{\lambda_{\textrm{\normalfont e}}\xspace}} 588 | \newcommand{\BCalcSE}{\ensuremath{\lambda_{\textrm{\normalfont se}}\xspace}} 589 | \newcommand{\BSPCF}{\ensuremath{\lambda^\rightarrow_{\textrm{\normalfont s}}\xspace}} 590 | \newcommand{\BEPCF}{\ensuremath{\lambda^\rightarrow_{\textrm{\normalfont e}}\xspace}} 591 | \newcommand{\BSEPCF}{\ensuremath{\lambda^\rightarrow_{\textrm{\normalfont se}}\xspace}} 592 | \newcommand{\IfZero}{\keyw{ifzero}} 593 | \newcommand{\Superpoint}{\lambda\_.\Do\;\Branch~\Unit} 594 | \newcommand{\ECount}{\dec{effcount}} 595 | \newcommand{\Countprog}{K} 596 | \newcommand{\Plus}{\mathsf{Plus}} 597 | \newcommand{\Minus}{\mathsf{Minus}} 598 | \newcommand{\Eq}{\mathsf{Eq}} 599 | \newcommand{\BList}{\mathbb{B}^\ast} 600 | 601 | \newcommand{\CtxCat}{\CatName{Ctx}} 602 | \newcommand{\PureCont}{\mathsf{PureCont}} 603 | 604 | \newcommand{\Addr}{\mathsf{Addr}} 605 | \newcommand{\Lab}{\mathsf{Lab}} 606 | \newcommand{\Env}{\mathsf{Env}} 607 | 608 | \newcommand{\Time}{\dec{DTIME}} 609 | \newcommand{\query}{\mathord{?}} 610 | \newcommand{\ans}{\mathord{!}} 611 | \newcommand{\labs}{\mathsf{labs}} 612 | \newcommand{\steps}{\mathsf{steps}} 613 | 614 | \newcommand{\tree}{\tau} 615 | \newcommand{\tl}{\labs(\tree)} 616 | \newcommand{\ts}{\steps(\tree)} 617 | \newcommand{\T}[1]{\ensuremath{\mathcal{T}_{#1}}} 618 | \newcommand{\Config}{\dec{Config}} 619 | \newcommand{\cekl}{\langle} 620 | \newcommand{\cekr}{\rangle} 621 | 622 | \newcommand{\const}[1]{\ulcorner #1 \urcorner} 623 | \newcommand{\HC}{\ensuremath{\mathcal{H}}} 624 | 625 | \newcommand{\tr}{\mathcal{T}} 626 | \newcommand{\tru}{\mathcal{U}} 627 | \newcommand{\Tree}{\dec{Tree}} 628 | \newcommand{\TimedTree}{\dec{TimedTree}} 629 | \newcommand{\denotep}[1]{\ensuremath{\mathbb{P}\llbracket #1 \rrbracket}} 630 | 631 | \newcommand\ttTwoTree{ 632 | \begin{tikzpicture}[->,>=stealth',level/.style={sibling distance = 4cm/##1, 633 | level distance = 2.0cm}] 634 | \node (root) [opnode] {Branch} 635 | child { node [opnode] {Branch} 636 | child { node [leaf] {$\True$} 637 | edge from parent node[above left] {$\True$} 638 | } 639 | child { node [leaf] {$\True$} 640 | edge from parent node[above right] {$\False$} 641 | } 642 | edge from parent node[above left] {$\True$} 643 | } 644 | child { node [opnode] {Branch} 645 | child { node [leaf] {$\True$} 646 | edge from parent node[above left] {$\True$} 647 | } 648 | child { node [leaf] {$\True$} 649 | edge from parent node[above right] {$\False$} 650 | } 651 | edge from parent node[above right] {$\False$} 652 | } 653 | ; 654 | \end{tikzpicture}} 655 | 656 | 657 | \newcommand{\tossTree}{ 658 | \begin{tikzpicture}[->,>=stealth',level/.style={sibling distance = 2.5cm/##1, 659 | level distance = 1.0cm}] 660 | \node (root) [opnode] {$\dec{Branch}$} 661 | child { node [leaf] {$\dec{Heads}$} 662 | edge from parent node[above left] {$\True$} 663 | } 664 | child { node [leaf] {$\dec{Tails}$} 665 | edge from parent node[above right] {$\False$} 666 | } 667 | ; 668 | \end{tikzpicture}} 669 | 670 | \newenvironment{twoeqs}{\ba[t]{@{}r@{~}c@{~}l@{~}c@{~}r@{~}c@{~}l@{}}}{\ea} 671 | 672 | \newcommand{\compTreeEx}{ 673 | \begin{tikzpicture}[->,>=stealth',level/.style={sibling distance = 2.0cm/##1, 674 | level distance = 2.0cm}] 675 | \node (root) [opnode] {$\getF$} 676 | child { node [yshift=15] {$\dots$} 677 | edge from parent {} 678 | } 679 | child { node [opnode] {$\putF$} 680 | child { node {$\True$} 681 | edge from parent node[left] {$\Unit$} 682 | } 683 | edge from parent node[yshift=5,left] {$-2$} 684 | } 685 | child { node [opnode] {$\putF$} 686 | child { node {$\False$} 687 | edge from parent node[left] {$\Unit$} 688 | } 689 | edge from parent node[yshift=2,left] {$-1$} 690 | } 691 | child { node [opnode] {$\putF$} 692 | child { node {$\True$} 693 | edge from parent node[left] {$\Unit$} 694 | } 695 | edge from parent node[left] {$0$} 696 | } 697 | child { node [opnode] {$\putF$} 698 | child { node {$\False$} 699 | edge from parent node[right] {$\Unit$} 700 | } 701 | edge from parent node[yshift=2,right] {$1$} 702 | } 703 | child { node [opnode] {$\putF$} 704 | child { node {$\True$} 705 | edge from parent node[right] {$\Unit$} 706 | } 707 | edge from parent node[yshift=5,right] {$2$} 708 | } 709 | child { node [yshift=15] {$\dots$} 710 | edge from parent {} 711 | } 712 | ; 713 | \end{tikzpicture}} 714 | 715 | \newcommand{\smath}[1]{\ensuremath{{\scriptstyle #1}}} 716 | 717 | \newcommand{\InfiniteModel}{% 718 | \begin{tikzpicture}[->,>=stealth',level/.style={sibling distance = 3.0cm/##1, 719 | level distance = 1.0cm}] 720 | \node (root) [draw=none] { } 721 | child { node [opnode] {$\smath{\query 0}$} 722 | child { node [opnode] {$\smath{\query 0}$} 723 | child { node [draw=none,rotate=165] {$\vdots$} 724 | edge from parent node { } 725 | } 726 | child { node[leaf] {$\smath{\ans\False}$} 727 | edge from parent node { } 728 | } 729 | edge from parent node { } 730 | } 731 | child { node [leaf] {$\smath{\ans\False}$} 732 | edge from parent node { } 733 | } 734 | edge from parent node { } 735 | } 736 | ; 737 | \end{tikzpicture}} 738 | % 739 | \newcommand{\ShortConjModel}{% 740 | \begin{tikzpicture}[->,>=stealth',level/.style={sibling distance = 3.5cm/##1, 741 | level distance = 1.0cm}] 742 | \node (root) [draw=none] { } 743 | child { node [opnode] {$\smath{\query 0}$} 744 | child { node [opnode] {$\smath{\query 0}$} 745 | child { node [treenode] {$\smath{\ans\True}$} 746 | edge from parent node { } 747 | } 748 | child { node[treenode] {$\smath{\ans\False}$} 749 | edge from parent node { } 750 | } 751 | edge from parent node { } 752 | } 753 | child { node [treenode] {$\smath{\ans\False}$} 754 | edge from parent node { } 755 | } 756 | edge from parent node { } 757 | } 758 | ; 759 | \end{tikzpicture}} 760 | % 761 | 762 | \newcommand{\TTTwoModel}{% 763 | \begin{tikzpicture}[->,>=stealth',level/.style={sibling distance = 8cm/##1, 764 | level distance = 1.5cm}] 765 | \node (root) [draw=none] { } 766 | child { node [opnode] {$\smath{\query 0}$} 767 | child { node [opnode] {$\smath{\query 1}$} 768 | child { node [leaf] {$\smath{\ans\True}$} 769 | edge from parent node { } 770 | } 771 | child { node[leaf] {$\smath{\ans\True}$} 772 | edge from parent node { } 773 | } 774 | edge from parent node { } 775 | } 776 | child { node [opnode] {$\smath{\query 1}$} 777 | child { node [leaf] {$\smath{\ans\True}$} 778 | edge from parent node { } 779 | } 780 | child { node[leaf] {$\smath{\ans\True}$} 781 | edge from parent node { } 782 | } 783 | edge from parent node { } 784 | } 785 | edge from parent node { } 786 | } 787 | ; 788 | \end{tikzpicture}} 789 | % 790 | \newcommand{\XORTwoModel}{% 791 | \begin{tikzpicture}[->,>=stealth',level/.style={sibling distance = 5.5cm/##1, 792 | level distance = 1cm}] 793 | \node (root) [draw=none] { } 794 | child { node [opnode] {$\smath{\query 0}$} 795 | child { node [opnode] {$\smath{\query 1}$} 796 | child { node [treenode] {$\smath{\ans\False}$} 797 | edge from parent node { } 798 | } 799 | child { node[treenode] {$\smath{\ans\True}$} 800 | edge from parent node { } 801 | } 802 | edge from parent node { } 803 | } 804 | child { node [opnode] {$\smath{\query 1}$} 805 | child { node [treenode] {$\smath{\ans\True}$} 806 | edge from parent node { } 807 | } 808 | child { node[treenode] {$\smath{\ans\False}$} 809 | edge from parent node { } 810 | } 811 | edge from parent node { } 812 | } 813 | edge from parent node { } 814 | } 815 | ; 816 | \end{tikzpicture}} 817 | % 818 | \newcommand{\SXORTwoModel}{% 819 | \begin{tikzpicture}[->,>=stealth',level/.style={sibling distance = 2.5cm/##1, 820 | level distance = 1cm}] 821 | \node (root) [opnode] {$\smath{\query 0}$} 822 | child { node [opnode] {$\smath{\query 1}$} 823 | child { node [treenode] {$\smath{\ans\False}$} 824 | edge from parent node { } 825 | } 826 | child { node[treenode] {$\smath{\ans\True}$} 827 | edge from parent node { } 828 | } 829 | edge from parent node { } 830 | } 831 | child { node [opnode] {$\smath{\query 1}$} 832 | child { node [treenode] {$\smath{\ans\True}$} 833 | edge from parent node { } 834 | } 835 | child { node[treenode] {$\smath{\ans\False}$} 836 | edge from parent node { } 837 | } 838 | edge from parent node { } 839 | } 840 | ; 841 | \end{tikzpicture}} 842 | % 843 | \newcommand{\TTZeroModel}{% 844 | \begin{tikzpicture}[->,>=stealth',level/.style={sibling distance = 1cm/##1, 845 | level distance = 1cm}] 846 | \node (root) [draw=none] { } 847 | child { node [treenode] {$\smath{\ans\True}$} 848 | edge from parent node { } 849 | } 850 | ; 851 | \end{tikzpicture}}% -------------------------------------------------------------------------------- /pkgs/mathpartir.sty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhil/phd-dissertation/81a4ecec0b6f36375a6abee7377f55723de4b05f/pkgs/mathpartir.sty -------------------------------------------------------------------------------- /pkgs/mathwidth.sty: -------------------------------------------------------------------------------- 1 | \def\fileversion{2e} 2 | \def\filedate{98/11/04} 3 | \NeedsTeXFormat{LaTeX2e} 4 | 5 | \ProvidesPackage{mathwidth}[{% 6 | \filedate\space\fileversion\space mathwidth package}] 7 | 8 | \@ifpackageloaded{lucbr}{}{% 9 | \DeclareMathVersion{hask} 10 | \SetMathAlphabet{\mathrm}{hask}{\encodingdefault}{\rmdefault}{m}{n}% 11 | \SetMathAlphabet{\mathbf}{hask}{\encodingdefault}{\rmdefault}{bx}{n}% 12 | \SetMathAlphabet{\mathsf}{hask}{\encodingdefault}{\sfdefault}{m}{n}% 13 | \DeclareSymbolFont{italics}{\encodingdefault}{\rmdefault}{m}{it}% 14 | \DeclareSymbolFontAlphabet{\mathrm}{operators} 15 | \DeclareSymbolFontAlphabet{\mathit}{letters} 16 | \DeclareSymbolFontAlphabet{\mathcal}{symbols} 17 | \DeclareSymbolFontAlphabet{\haskit}{italics} 18 | \mathversion{hask} 19 | } 20 | 21 | % This next bit looks remarkably similar to part of zed.sty. It's used 22 | % to change the spacing so that identifiers look nicer... 23 | \def\@setmcodes#1#2#3{{\count0=#1 \count1=#3 24 | \loop \global\mathcode\count0=\count1 \ifnum \count0<#2 25 | \advance\count0 by1 \advance\count1 by1 \repeat}} 26 | \@setmcodes{`A}{`Z}{"7\hexnumber@\symitalics41} 27 | \@setmcodes{`a}{`z}{"7\hexnumber@\symitalics61} 28 | 29 | -------------------------------------------------------------------------------- /slides/Makefile: -------------------------------------------------------------------------------- 1 | TEXC=pdflatex 2 | CFLAGS=-interaction=nonstopmode -halt-on-error -file-line-error 3 | BIBC=bibtex 4 | PAPER=viva 5 | BIBLIO=$(PAPER) 6 | LATEST_COMMIT=$(shell git log --format="%h" -n 1) 7 | 8 | all: $(PAPER).pdf 9 | draft: $(PAPER).pdf-draft 10 | 11 | $(PAPER).aux: $(PAPER).tex 12 | $(TEXC) $(CFLAGS) $(PAPER) 13 | 14 | $(BIBLIO).bbl: $(PAPER).aux $(BIBLIO).bib 15 | $(BIBC) $(PAPER) 16 | 17 | $(PAPER).pdf: $(PAPER).aux $(BIBLIO).bbl 18 | $(TEXC) $(CFLAGS) $(PAPER) 19 | $(TEXC) $(CFLAGS) $(PAPER) 20 | 21 | $(PAPER).pdf-draft: CFLAGS:=$(CFLAGS) "\def\DRAFT{$(LATEST_COMMIT)}\input{$(PAPER)}" 22 | $(PAPER).pdf-draft: all 23 | mv $(PAPER).pdf $(PAPER)-draft.pdf 24 | tar cf thesis-draft.tar.gz $(PAPER)-draft.pdf 25 | 26 | clean: 27 | rm -f *.log *.aux *.toc *.out 28 | rm -f *.bbl *.blg *.fls *.xml 29 | rm -f *.nav *.snm 30 | rm -f *.fdb_latexmk *.vtc *.cut 31 | rm -f $(PAPER).pdf camera-ready.pdf submission.pdf 32 | rm -f *.o *.cmx *.cmo 33 | -------------------------------------------------------------------------------- /slides/viva.bib: -------------------------------------------------------------------------------- 1 | @inproceedings{HillerstromL16, 2 | author = {Daniel Hillerstr{\"{o}}m and 3 | Sam Lindley}, 4 | title = {Liberating effects with rows and handlers}, 5 | booktitle = {TyDe@ICFP}, 6 | pages = {15--27}, 7 | publisher = {{ACM}}, 8 | year = {2016} 9 | } 10 | 11 | @inproceedings{HillerstromLAS17, 12 | author = {Daniel Hillerstr{\"{o}}m and 13 | Sam Lindley and 14 | Robert Atkey and 15 | {KC} Sivaramakrishnan}, 16 | title = {Continuation Passing Style for Effect Handlers}, 17 | booktitle = {{FSCD}}, 18 | series = {LIPIcs}, 19 | volume = {84}, 20 | pages = {18:1--18:19}, 21 | OPTpublisher = {Schloss Dagstuhl - Leibniz-Zentrum fuer Informatik}, 22 | year = {2017} 23 | } 24 | 25 | @inproceedings{HillerstromL18, 26 | author = {Daniel Hillerstr{\"{o}}m and 27 | Sam Lindley}, 28 | title = {Shallow Effect Handlers}, 29 | booktitle = {{APLAS}}, 30 | OPTseries = {Lecture Notes in Computer Science}, 31 | series = {{LNCS}}, 32 | volume = {11275}, 33 | pages = {415--435}, 34 | publisher = {Springer}, 35 | year = {2018} 36 | } 37 | 38 | @article{HillerstromLA20, 39 | author = {Daniel Hillerstr{\"{o}}m and 40 | Sam Lindley and 41 | Robert Atkey}, 42 | title = {Effect handlers via generalised continuations}, 43 | journal = {J. Funct. Program.}, 44 | volume = {30}, 45 | pages = {e5}, 46 | year = {2020} 47 | } 48 | 49 | @article{HillerstromLL20, 50 | author = {Daniel Hillerstr{\"{o}}m and 51 | Sam Lindley and 52 | John Longley}, 53 | title = {Effects for Efficiency: Asymptotic Speedup with First-Class Control}, 54 | journal = {Proc. {ACM} Program. Lang.}, 55 | volume = {4}, 56 | number = {{ICFP}}, 57 | pages = {100:1--100:29}, 58 | year = {2020} 59 | } 60 | 61 | # Unix 62 | @article{RitchieT74, 63 | author = {Dennis Ritchie and 64 | Ken Thompson}, 65 | title = {The {UNIX} Time-Sharing System}, 66 | journal = {Commun. {ACM}}, 67 | volume = {17}, 68 | number = {7}, 69 | pages = {365--375}, 70 | year = {1974} 71 | } 72 | 73 | # CEK & C 74 | @InProceedings{FelleisenF86, 75 | title={Control Operators, the {SECD}-machine, and the $\lambda$-Calculus}, 76 | author={Felleisen, Matthias and Friedman, Daniel P.}, 77 | year=1987, 78 | booktitle = {Formal Description of Programming Concepts III}, 79 | OPTbooktitle = {The Proceedings of the Conference on Formal Description of Programming Concepts III, Ebberup, Denmark}, 80 | pages = {193--217}, 81 | OPTpublisher={North Holland} 82 | } -------------------------------------------------------------------------------- /slides/viva.tex: -------------------------------------------------------------------------------- 1 | \documentclass[169,10pt,compress,dvipsnames]{beamer} 2 | %% 3 | %% Slides layout 4 | %% 5 | \beamertemplatenavigationsymbolsempty % hides navigation buttons 6 | \usetheme{Madrid} % standard Madrid theme 7 | \setbeamertemplate{footline}{} % renders the footer empty 8 | % 9 | \setbeamertemplate{bibliography item}{ % this is a hack to prevent Madrid theme + biblatex 10 | \hspace{-0.4cm}\lower3pt\hbox{ % from causing bibliography entries to run over 11 | \pgfuseimage{beamericonarticle} % the slide margins 12 | }} 13 | 14 | %% 15 | %% Packages 16 | %% 17 | \usepackage[utf8]{inputenc} % enable UTF-8 compatible typing 18 | \usepackage{hyperref} % interactive PDF 19 | \usepackage[sort&compress,square,numbers]{natbib} % Bibliography 20 | \usepackage{bibentry} % Print bibliography entries inline. 21 | \makeatletter % Redefine bibentry to omit hyperrefs 22 | \renewcommand\bibentry[1]{\nocite{#1}{\frenchspacing 23 | \@nameuse{BR@r@#1\@extra@b@citeb}}} 24 | \makeatother 25 | \nobibliography* % use the bibliographic data from the standard BibTeX setup. 26 | \usepackage{amsmath,amssymb,mathtools} % maths typesetting 27 | \usepackage{../pkgs/mathpartir} % Inference rules 28 | \usepackage{../pkgs/mathwidth} % renders character sequences nicely in math mode 29 | \usepackage{stmaryrd} % semantic brackets 30 | \usepackage{xspace} % proper spacing for macros in text 31 | 32 | \usepackage[T1]{fontenc} % 8-bit font encoding 33 | % native support for accented characters. 34 | \usepackage[scaled=0.85]{beramono} % smoother typewriter font 35 | \newcommand*{\Scale}[2][4]{\scalebox{#1}{\ensuremath{#2}}}% 36 | 37 | \input{../macros.tex} 38 | 39 | %% 40 | %% Meta information 41 | %% 42 | \author{Daniel Hillerström} 43 | \title{Foundations for Programming and Implementing Effect Handlers} 44 | \institute{The University of Edinburgh, Scotland UK} 45 | \subtitle{PhD viva} 46 | \date{August 13, 2021} 47 | 48 | %% 49 | %% Slides 50 | %% 51 | \begin{document} 52 | 53 | % 54 | % Title slide 55 | % 56 | \begin{frame} 57 | \maketitle 58 | \end{frame} 59 | 60 | % Dissertation overview 61 | \begin{frame} 62 | \frametitle{My dissertation at glance} 63 | 64 | Three main strands of work 65 | 66 | \begin{description} 67 | \item[Programming] Language design and applications of effect handlers. 68 | \item[Implementation] Canonical implementation strategies for effect handlers. 69 | \item[Expressiveness] Exploration of the computational expressiveness of effect handlers. 70 | \end{description} 71 | \end{frame} 72 | 73 | \begin{frame} 74 | \frametitle{Calculi for deep, shallow, and parameterised handlers} 75 | 76 | The calculi capture key aspects of the implementation of effect 77 | handlers in Links. 78 | 79 | \begin{itemize} 80 | \item $\HCalc$ ordinary deep handlers (fold). 81 | \item $\SCalc$ shallow handlers (case-split). 82 | \item $\HPCalc$ parameterised deep handlers (fold+state). 83 | \end{itemize} 84 | 85 | The actual implementation is the union of the three calculi.\\[2em] 86 | 87 | \textbf{Relevant papers} TyDe'16~\cite{HillerstromL16}, 88 | APLAS'18~\cite{HillerstromL18}, JFP'20~\cite{HillerstromLA20}. 89 | \end{frame} 90 | 91 | % UNIX 92 | \begin{frame} 93 | \frametitle{Effect handlers as composable operating systems} 94 | 95 | An interpretation of \citeauthor{RitchieT74}'s 96 | UNIX~\cite{RitchieT74} in terms of effect handlers.\\[2em] 97 | 98 | \[ 99 | \bl 100 | \!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\textbf{Basic idea} 101 | \ba[m]{@{\qquad}r@{~}c@{~}l} 102 | \text{\emph{system call}} &\approx& \text{\emph{operation invocation}}\\ 103 | \text{\emph{system call implementation}} &\approx& \text{\emph{operation interpretation}} 104 | \ea 105 | \el 106 | \]\hfill\\[2em] 107 | 108 | \textbf{Key point} Legacy code is modularly retrofitted with functionality. 109 | \end{frame} 110 | 111 | % CPS translation 112 | \begin{frame} 113 | \frametitle{CPS transforms for effect handlers} 114 | 115 | A higher-order CPS transform for effect handlers with generalised 116 | continuations.\\[1em] 117 | 118 | \textbf{Generalised continuation} Structured representation of 119 | delimited continuations.\\[0.5em] 120 | 121 | \[ 122 | \Scale[1.8]{\kappa = \overline{(\sigma, (\hret,\hops))}} 123 | \]\\[1em] 124 | 125 | \textbf{Key point} Separate the \emph{doing} layer ($\sigma$) from the \emph{being} layer ($H$).\\[2em] 126 | 127 | \textbf{Relevant papers} FSCD'17~\cite{HillerstromLAS17}, 128 | APLAS'18~\cite{HillerstromL18}, JFP'20~\cite{HillerstromLA20}. 129 | \end{frame} 130 | 131 | % Abstract machine 132 | \begin{frame} 133 | \frametitle{Abstract machine semantics for effect handlers} 134 | 135 | Plugging generalised continuations into \citeauthor{FelleisenF86}'s 136 | CEK machine~\cite{FelleisenF86} yields a runtime for effect 137 | handlers.\\[2em] 138 | 139 | \[ 140 | \Scale[2]{\cek{C \mid E \mid K = \overline{(\sigma, (H,E))}}} 141 | \]\\[2em] 142 | 143 | \textbf{Relevant papers} TyDe'16~\cite{HillerstromL16}, 144 | JFP'20~\cite{HillerstromLA20}. 145 | 146 | \end{frame} 147 | 148 | % Interdefinability of handlers 149 | \begin{frame} 150 | \frametitle{Interdefinability of effect handlers} 151 | 152 | Deep, shallow, and parameterised handlers are interdefinable 153 | w.r.t. to typability-preserving macro-expressiveness. 154 | 155 | \begin{itemize} 156 | \item Deep as shallow, $\mathcal{D}\llbracket - \rrbracket$, image is computationally lightweight. 157 | \item Shallow as deep, $\mathcal{S}\llbracket - \rrbracket$, image is computationally expensive. 158 | \item Parameterised as deep, $\mathcal{P}\llbracket - \rrbracket$, 159 | image uses explicit state-passing. 160 | \end{itemize} 161 | ~\\[1em] 162 | \textbf{Relevant papers} APLAS'18~\cite{HillerstromL18}, 163 | JFP'20~\cite{HillerstromLA20}. 164 | 165 | \end{frame} 166 | 167 | % Asymptotic speed up with first-class control 168 | \begin{frame} 169 | \frametitle{Asymptotic speed up with effect handlers} 170 | 171 | Effect handlers can make some programs faster! 172 | 173 | \[ 174 | \Count_n : ((\Nat_n \to \Bool) \to \Bool) \to \Nat 175 | \]\\[1em] 176 | % 177 | Using type-respecting expressiveness 178 | \begin{itemize} 179 | \item There \textbf{exists} an implementation of $\Count_n \in \HPCF$ with 180 | effect handlers such that the runtime for every $n$-standard predicate $P$ is 181 | $\Count_n~P = \BigO(2^n)$. 182 | \item \textbf{Forall} implementations of $\Count_n \in \BPCF$ the runtime for every $n$-standard predicate $P$ is $\Count_n~P = \Omega(n2^n)$ 183 | \end{itemize} 184 | ~\\[1em] 185 | \textbf{Relevant paper} ICFP'20~\cite{HillerstromLL20}. 186 | \end{frame} 187 | 188 | % Background 189 | % \begin{frame} 190 | % \frametitle{Continuations literature review} 191 | % \end{frame} 192 | 193 | % 194 | % References 195 | % 196 | \begin{frame}%[allowframebreaks] 197 | \frametitle{References} 198 | \bibliographystyle{plainnat} 199 | \bibliography{\jobname} 200 | \end{frame} 201 | \end{document} --------------------------------------------------------------------------------