on master help sign in
Add section on composing state machines
commita6745400b00237ded92aa644ee5cfbae0ca6cd15 authorEdwin Brady <ecb10@st-andrews.ac.uk> dateFri 24 Mar 2017, 17:16 (10 years ago) parenta89330e
13 files changed+1238−14
docs/st/composing.rst+475−1 docs/st/machines.rst+2−13 samples/ST/Composite.idr+8 samples/ST/Graphics/Draw.idr+88 samples/ST/Graphics/Turtle.idr+116 samples/ST/Intro.idr+75 samples/ST/Login.idr+55 samples/ST/LoginCount.idr+77 samples/ST/Net/EchoSimple.idr+27 samples/ST/Net/Network.idr+107 samples/ST/Net/RandServer.idr+156 samples/ST/Net/Threads.idr+23 samples/ST/TreeTag.idr+29
docs/st/composing.rst+475−1
@@ -295,6 +295,480 @@ the overall resources.
295295Composite resources: Hierarchies of state machines
296296==================================================
297297
298−Composite resources: Graphics
298+We've now seen how to use multiple resources in one function, which is
299+necessary for any realistic program which manipulates state. We can think
300+of this as "horizontal" composition: using multiple resources at once.
301+We'll often also need "vertical" composition: implementing one resource
302+in terms of one or more other resources.
303+
304+We'll see an example of this in this section. First, we'll implement a
305+small API for graphics, in an interface ``Draw``, supporting:
306+
307+* Opening a window, creating a double-buffered surface to draw on
308+* Drawing lines and rectangles onto a surface
309+* "Flipping" buffers, displaying the surface we've just drawn onto in
310+ the window
311+* Closing a window
312+
313+Then, we'll use this API to implement a higher level API for turtle graphics,
314+in an ``interface``.
315+This will require not only the ``Draw`` interface, but also a representation
316+of the turtle state (location, direction and pen colour).
317+
318+.. topic:: SDL bindings
319+
320+ For the examples in this section, you'll need to install the
321+ (very basic!) SDL bindings for Idris, available from
322+ https://github.com/edwinb/SDL-idris. These bindings implement a small
323+ subset of the SDL API, and are for illustrative purposes only.
324+ Nevertheless, they are enough to implement small graphical programs
325+ and demonstrate the concepts of this section.
326+
327+ Once you've installed this package, you can start Idris with the
328+ ``-p sdl`` flag, for the SDL bindings, and the ``-p contrib`` flag,
329+ for the ``Control.ST`` library.
330+
331+The ``Draw`` interface
332+----------------------
333+
334+We're going to use the Idris SDL bindings for this API, so you'll need
335+to import ``Graphics.SDL`` once you've installed the bindings.
336+We'll start by defining the ``Draw`` interface, which includes a data type
337+representing a surface on which we'll draw lines and rectangles:
299338
339+.. code-block:: idris
340+
341+ interface Draw (m : Type -> Type) where
342+ Surface : Type
343+
344+We'll need to be able to create a new ``Surface`` by opening a window:
345+
346+.. code-block:: idris
347+
348+ initWindow : Int -> Int -> ST m Var [add Surface]
349+
350+However, this isn't quite right. It's possible that opening a window
351+will fail, for example if our program is running in a termina without
352+a windowing system available. So, somehow, ``initWindow`` needs to cope
353+with the possibility of failure. We can do this by returning a
354+``Maybe Var``, rather than a ``Var``, and only adding the ``Surface``
355+on success:
356+
357+.. code-block:: idris
358+
359+ initWindow : Int -> Int -> ST m (Maybe Var) [addIfJust Surface]
360+
361+This uses a type level function ``addIfJust``, defined in ``Control.ST``
362+which returns an ``Action`` that only adds a resource if the operation
363+succeeds (that is, returns a result of the form ``Just val``.
364+
365+.. topic:: ``addIfJust`` and ``addIfRight``
366+
367+ ``Control.ST`` defines functions for constructing new resources if an
368+ operation succeeds. As well as ``addIfJust``, which adds a resource if
369+ an operation returns ``Just ty``, there's also ``addIfRight``:
370+
371+ .. code-block:: idris
372+
373+ addIfJust : Type -> Action (Maybe Var)
374+ addIfRight : Type -> Action (Either a Var)
375+
376+ Each of these is implemented in terms of the following primitive action
377+ ``Add``, which takes a function to construct a resource list from the result
378+ of an operation:
379+
380+ .. code-block:: idris
381+
382+ Add : (ty -> Resources) -> Action ty
383+
384+ Using this, you can create your own actions to add resources
385+ based on the result of an operation, if required. For example,
386+ ``addIfJust`` is implemented as follows:
387+
388+ .. code-block:: idris
389+
390+ addIfJust : Type -> Action (Maybe Var)
391+ addIfJust ty = Add (maybe [] (\var => [var ::: ty]))
392+
393+If we create windows, we'll also need to be able to delete them:
394+
395+.. code-block:: idris
396+
397+ closeWindow : (win : Var) -> ST m () [remove win Surface]
398+
399+We'll also need to respond to events such as keypresses and mouse clicks.
400+The ``Graphics.SDL`` library provides an ``Event`` type for this, and
401+we can ``poll`` for events which returns the last event which occurred,
402+if any:
403+
404+.. code-block:: idris
405+
406+ poll : ST m (Maybe Event) []
407+
408+The remaining methods of ``Draw`` are ``flip``, which flips the buffers
409+displaying everything that we've drawn since the previous ``flip``, and
410+two methods for drawing: ``filledRectangle`` and ``drawLine``.
411+
412+.. code-block:: idris
413+
414+ flip : (win : Var) -> ST m () [win ::: Surface]
415+ filledRectangle : (win : Var) -> (Int, Int) -> (Int, Int) -> Col -> ST m () [win ::: Surface]
416+ drawLine : (win : Var) -> (Int, Int) -> (Int, Int) -> Col -> ST m () [win ::: Surface]
417+
418+We define colours as follows, as four components (red, green, blue, alpha):
419+
420+.. code-block:: idris
421+
422+ data Col = MkCol Int Int Int Int
423+
424+ black : Col
425+ black = MkCol 0 0 0 255
426+
427+ red : Col
428+ red = MkCol 255 0 0 255
429+
430+ green : Col
431+ green = MkCol 0 255 0 255
432+
433+ -- Also blue, yellow, magenta, cyan, white, similarly...
434+
435+If you import ``Graphics.SDL``, you can implement the ``Draw`` interface
436+using the SDL bindings as follows:
437+
438+.. code-block:: idris
439+
440+ interface Draw IO where
441+ Surface = State SDLSurface
442+
443+ initWindow x y = do Just srf <- lift (startSDL x y)
444+ | pure Nothing
445+ var <- new srf
446+ pure (Just var)
447+
448+ closeWindow win = do lift endSDL
449+ delete win
450+
451+ flip win = do srf <- read win
452+ lift (flipBuffers srf)
453+ poll = lift pollEvent
454+
455+ filledRectangle win (x, y) (ex, ey) (MkCol r g b a)
456+ = do srf <- read win
457+ lift $ filledRect srf x y ex ey r g b a
458+ drawLine win (x, y) (ex, ey) (MkCol r g b a)
459+ = do srf <- read win
460+ lift $ drawLine srf x y ex ey r g b a
461+
462+In this implementation, we've used ``startSDL`` to initialise a window, which,
463+returns ``Nothing`` if it fails. Since the type of ``initWindow`` states that
464+it adds a resource when it returns a value of the form ``Just val``, we
465+add the surface returned by ``startSDL`` on success, and nothing on
466+failure. We can only successfully initialise if ``startDSL`` succeeds.
467+
468+Now that we have an implementation of ``Draw``, we can try writing some
469+functions for drawing into a window and execute them via the SDL bindings.
470+For example, assuming we have a surface ``win`` to draw onto, we can write a
471+``render`` function as follows which draws a line onto a black background:
472+
473+.. code-block:: idris
474+
475+ render : Draw m => (win : Var) -> ST m () [win ::: Surface {m}]
476+ render win = do filledRectangle win (0,0) (640,480) black
477+ drawLine win (100,100) (200,200) red
478+ flip win
479+
480+The ``flip win`` at the end is necessary because the drawing primitives
481+are double buffered, to prevent flicker. We draw onto one buffer, off-screen,
482+and display the other. When we call ``flip``, it displays the off-screen
483+buffer, and creates a new off-screen buffer for drawing the next frame.
484+
485+To include this in a program, we'll write a main loop which renders our
486+image and waits for an event to indicate the user wants to close the
487+application:
488+
489+.. code-block:: idris
490+
491+ loop : Draw m => (win : Var) -> ST m () [win ::: Surface {m}]
492+ loop win = do render win
493+ Just AppQuit <- poll
494+ | _ => loop win
495+ pure ()
496+
497+Finally, we can create a main program which initialises a window, if
498+possible, then runs the main loop:
499+
500+.. code-block:: idris
501+
502+ drawMain : (ConsoleIO m, Draw m) => ST m () []
503+ drawMain = do Just win <- initWindow 640 480
504+ | Nothing => putStrLn "Can't open window"
505+ loop win
506+ closeWindow win
507+
508+We can try this at the REPL using ``run``:
509+
510+.. code::
511+
512+ *Draw> :exec run drawMain
513+
514+A higher level interface: ``TurtleGraphics``
515+--------------------------------------------
516+
517+Turtle graphics involves a "turtle" moving around the screen, drawing a line as
518+it moves with a "pen". A turtle has attributes describing its location, the
519+direction it's facing, and the current pen colour. There are commands for
520+moving the turtle forwards, turning through an angle, and changing the
521+pen colour, among other things. One possible interface would be the
522+following:
523+
524+.. code-block:: idris
525+
526+ interface TurtleGraphics (m : Type -> Type) where
527+ Turtle : Type
528+
529+ start : Int -> Int -> ST m (Maybe Var) [addIfJust Turtle]
530+ end : (t : Var) -> ST m () [Remove t Turtle]
531+
532+ fd : (t : Var) -> Int -> ST m () [t ::: Turtle]
533+ rt : (t : Var) -> Int -> ST m () [t ::: Turtle]
534+
535+ penup : (t : Var) -> ST m () [t ::: Turtle]
536+ pendown : (t : Var) -> ST m () [t ::: Turtle]
537+ col : (t : Var) -> Col -> ST m () [t ::: Turtle]
538+
539+ render : (t : Var) -> ST m () [t ::: Turtle]
540+
541+Like ``Draw``, we have a command for initialising the turtle (here called
542+``start``) which might fail if it can't create a surface for the turtle to
543+draw on. There is also a ``render`` method, which is intended to render the
544+picture drawn so far in a window. One possible program with this interface
545+is the following, with draws a colourful square:
546+
547+.. code-block:: idris
548+
549+ turtle : (ConsoleIO m, TurtleGraphics m) => ST m () []
550+ turtle = with ST do
551+ Just t <- start 640 480
552+ | Nothing => putStr "Can't make turtle\n"
553+ col t yellow
554+ fd t 100; rt t 90
555+ col t green
556+ fd t 100; rt t 90
557+ col t red
558+ fd t 100; rt t 90
559+ col t blue
560+ fd t 100; rt t 90
561+ render t
562+ end t
563+
564+.. topic:: ``with ST do``
565+
566+ The purpose of ``with ST do`` in ``turtle`` is to disambiguate ``(>>=)``,
567+ which could be either the version from the ``Monad`` interface, or the
568+ version from ``ST``. Idris can work this out itself, but it takes time to
569+ try all of the possibilities, so the ``with`` clause can
570+ speed up type checking.
571+
572+To implement the interface, we could try using ``Surface`` to represent
573+the surface for the turtle to draw on:
574+
575+.. code-block:: idris
576+
577+ implementation Draw m => TurtleGraphics m where
578+ Turtle = Surface {m}
579+
580+Knowing that a ``Turtle`` is represented as a ``Surface``, we can use the
581+methods provided by ``Draw`` to implement the turtle. Unfortunately, though,
582+this isn't quite enough. We need to store more information: in particular, the
583+turtle has several attributes which we need to store somewhere.
584+So, not only do we need to represent the turtle as a ``Surface``, we need
585+to store some additional state. We can achieve this using a *composite*
586+resource.
587+
588+Introducing composite resources
589+-------------------------------
590+
591+A *composite* resource is built up from a list of other resources, and
592+is implemented using the following type, defined by ``Control.ST``:
593+
594+.. code-block:: idris
595+
596+ data Composite : List Type -> Type
597+
598+If we have a composite resource, we can split it into its constituent
599+resources, and create new variables for each of those resources, using
600+the *split* function. For example:
601+
602+.. code-block:: idris
603+
604+ splitComp : (comp : Var) -> ST m () [comp ::: Composite [State Int, State String]]
605+ splitComp comp = do [int, str] <- split comp
606+ ?whatNow
607+
608+The call ``split comp`` extracts the ``State Int`` and ``State String`` from
609+the composite resource ``comp``, and stores them in the variables ``int``
610+and ``str`` respectively. If we check the type of ``whatNow``, we'll see
611+how this has affected the resource list:
612+
613+.. code-block:: idris
614+
615+ int : Var
616+ str : Var
617+ comp : Var
618+ m : Type -> Type
619+ --------------------------------------
620+ whatNow : STrans m () [int ::: State Int, str ::: State String, comp ::: State ()]
621+ (\result => [comp ::: Composite [State Int, State String]])
622+
623+So, we have two new resources ``int`` and ``str``, and the type of
624+``comp`` has been updated to the unit type, so currently holds no data.
625+This is to be expected: we've just extracted the data into individual
626+resources after all.
627+
628+Now that we've extracted the individual resources, we can manipulate them
629+directly (say, incrementing the ``Int`` and adding a newline to the
630+``String``) then rebuild the composite resource using ``combine``:
631+
632+.. code-block:: idris
633+
634+ splitComp : (comp : Var) ->
635+ ST m () [comp ::: Composite [State Int, State String]]
636+ splitComp comp = do [int, str] <- split comp
637+ update int (+ 1)
638+ update str (++ "\n")
639+ combine comp [int, str]
640+ ?whatNow
641+
642+As ever, we can check the type of ``whatNow`` to see the effect of
643+``combine``:
644+
645+.. code-block:: idris
646+
647+ comp : Var
648+ int : Var
649+ str : Var
650+ m : Type -> Type
651+ --------------------------------------
652+ whatNow : STrans m () [comp ::: Composite [State Int, State String]]
653+ (\result => [comp ::: Composite [State Int, State String]])
654+
655+The effect of ``combine``, therefore, is to take existing
656+resources and merge them into one composite resource. Before we run
657+``combine``, the target resource must exist (``comp`` here) and must be
658+of type ``State ()``.
659+
660+It is instructive to look at the types of ``split`` and combine to see
661+the requirements on resource lists they work with. The type of ``split``
662+is the following:
663+
664+.. code-block:: idris
665+
666+ split : (lbl : Var) -> {auto prf : InState lbl (Composite vars) res} ->
667+ STrans m (VarList vars) res (\ vs => mkRes vs ++ updateRes res prf (State ()))
668+
669+The implicit ``prf`` argument says that the ``lbl`` being split must be
670+a composite resource. It returns a variable list, built from the composite
671+resource, and the ``mkRes`` function makes a list of resources of the
672+appropriate types. Finally, ``updateRes`` updates the composite resource to
673+have the type ``State ()``.
674+
675+The ``combine`` function does the inverse:
676+
677+.. code-block:: idris
678+
679+ combine : (comp : Var) -> (vs : List Var) ->
680+ {auto prf : InState comp (State ()) res} ->
681+ {auto var_prf : VarsIn (comp :: vs) res} ->
682+ STrans m () res (const (combineVarsIn res var_prf))
683+
684+The implicit ``prf`` argument here ensures that the target resource ``comp``
685+has type ``State ()``. That is, we're not overwriting any other data.
686+The implicit ``var_prf`` argument is similar to ``SubRes`` in ``call``, and
687+ensures that every variable we're using to build the composite resource
688+really does exist in the current resource list.
689+
690+We can use composite resources to implement our higher level ``TurtleGraphics``
691+API in terms of ``Draw``, and any additional resources we need.
692+
693+Implementing ``Turtle``
694+-----------------------
695+
696+Now that we've seen how to build a new resource from an existing collection,
697+we can implement ``Turtle`` using a composite resource, containing the
698+``Surface`` to draw on, and individual states for the pen colour and the
699+pen location and direction. We also have a list of lines, which describes
700+what we'll draw onto the ``Surface`` when we call ``render``:
701+
702+.. code-block:: idris
703+
704+ Turtle = Composite [Surface {m}, -- surface to draw on
705+ State Col, -- pen colour
706+ State (Int, Int, Int, Bool), -- pen location/direction/d
707+ State (List Line)] -- lines to draw on render
708+
709+A ``Line`` is defined as a start location, and end location, and a colour:
710+
711+.. code-block:: idris
712+
713+ Line : Type
714+ Line = ((Int, Int), (Int, Int), Col)
715+
716+To implement ``start``, which creates a new ``Turtle`` (or returns ``Nothing``)
717+if this is impossible, we begin by initialising the drawing surface then
718+all of the components of the state. Finally, we combine all of these
719+into a composite resource for the turtle:
720+
721+.. code-block:: idris
722+
723+ start x y = do Just srf <- initWindow x y
724+ | Nothing => pure Nothing
725+ col <- new white
726+ pos <- new (320, 200, 0, True)
727+ lines <- new []
728+ turtle <- new ()
729+ combine turtle [srf, col, pos, lines]
730+ pure (Just turtle)
731+
732+To implement ``end``, which needs to dispose of the turtle,
733+we deconstruct the composite resource, close the window,
734+then remove each individual resource. Remember that we can only ``delete``
735+a ``State``, so we need to ``split`` the composite resource, close the
736+drawing surface cleanly with ``closeWindow``, then ``delete`` the states:
737+
738+.. code-block:: idris
739+
740+ end t = do [srf, col, pos, lines] <- split t
741+ closeWindow srf; delete col; delete pos; delete lines; delete t
742+
743+For the other methods, we need to ``split`` the resource to get each
744+component, and ``combine`` into a composite resource when we're done.
745+As an example, here's ``penup``:
746+
747+.. code-block:: idris
748+
749+ penup t = do [srf, col, pos, lines] <- split t -- Split the composite resource
750+ (x, y, d, _) <- read pos -- Deconstruct the pen position
751+ write pos (x, y, d, False) -- Set the pen down flag to False
752+ combine t [srf, col, pos, lines] -- Recombine the components
753+
754+The remaining operations on the turtle follow a similar pattern. See
755+``samples/ST/Graphics/Turtle.idr`` in the Idris distribution for the full
756+details. It remains to render the image created by the turtle:
757+
758+.. code-block:: idris
300759
760+ render t = do [srf, col, pos, lines] <- split t -- Split the composite resource
761+ filledRectangle srf (0, 0) (640, 480) black -- Draw a background
762+ drawAll srf !(read lines) -- Render the lines drawn by the turtle
763+ flip srf -- Flip the buffers to display the image
764+ combine t [srf, col, pos, lines]
765+ Just ev <- poll
766+ | Nothing => render t -- Keep going until a key is pressed
767+ case ev of
768+ KeyUp _ => pure () -- Key pressed, so quit
769+ _ => render t
770+ where drawAll : (srf : Var) -> List Line -> ST m () [srf ::: Surface {m}]
771+ drawAll srf [] = pure ()
772+ drawAll srf ((start, end, col) :: xs)
773+ = do drawLine srf start end col -- Draw a line in the appropriate colour
774+ drawAll srf xs
docs/st/machines.rst+2−13
@@ -56,18 +56,7 @@ a data store *interface* and define a concrete type later:
5656.. code-block:: idris
5757
5858 interface DataStore (m : Type -> Type) where
59− data Store : Access -> Type
60−
61−.. topic:: ``data`` in interfaces
62−
63− The ``data`` keyword in the interface means that ``Store`` must be
64− implemented by a concrete data type in any implementation of
65− ``DataStore``. By doing this, Idris can use this
66− information to help in type-checking. For example, if a constraint arises
67− during type checking that ``State x`` must be the same as ``State y``,
68− in the same implementation,
69− Idris can assume that ``x`` must be the same as ``y``, because ``State``
70− must be implemented by the same data type.
59+ Store : Access -> Type
7160
7261We can continue to populate this interface with operations on the store. Among
7362other advantages, by separating the *interface* from its *implementation* we
@@ -213,7 +202,7 @@ This completes the interface, repeated in full for reference below:
213202.. code-block:: idris
214203
215204 interface DataStore (m : Type -> Type) where
216− data Store : Access -> Type
205+ Store : Access -> Type
217206
218207 connect : ST m Var [add (Store LoggedOut)]
219208 disconnect : (store : Var) -> ST m () [remove store (Store LoggedOut)]
samples/ST/Composite.idrnew file+8
@@ -0,0 +1,8 @@
1+import Control.ST
2+
3+splitComp : (comp : Var) ->
4+ ST m () [comp ::: Composite [State Int, State String]]
5+splitComp comp = do [int, str] <- split comp
6+ update int (+ 1)
7+ update str (++ "\n")
8+ combine comp [int, str]
samples/ST/Graphics/Draw.idrnew file+88
@@ -0,0 +1,88 @@
1+module Draw
2+
3+import public Graphics.SDL
4+import Control.ST
5+import Control.ST.ImplicitCall
6+
7+%access public export
8+
9+data Col = MkCol Int Int Int Int
10+
11+black : Col
12+black = MkCol 0 0 0 255
13+
14+red : Col
15+red = MkCol 255 0 0 255
16+
17+green : Col
18+green = MkCol 0 255 0 255
19+
20+blue : Col
21+blue = MkCol 0 0 255 255
22+
23+cyan : Col
24+cyan = MkCol 0 255 255 255
25+
26+magenta : Col
27+magenta = MkCol 255 0 255 255
28+
29+yellow : Col
30+yellow = MkCol 255 255 0 255
31+
32+white : Col
33+white = MkCol 255 255 255 255
34+
35+interface Draw (m : Type -> Type) where
36+ Surface : Type
37+
38+ initWindow : Int -> Int -> ST m (Maybe Var) [addIfJust Surface]
39+ closeWindow : (win : Var) -> ST m () [remove win Surface]
40+
41+ flip : (win : Var) -> ST m () [win ::: Surface]
42+ poll : ST m (Maybe Event) []
43+
44+ filledRectangle : (win : Var) -> (Int, Int) -> (Int, Int) -> Col ->
45+ ST m () [win ::: Surface]
46+ drawLine : (win : Var) -> (Int, Int) -> (Int, Int) -> Col ->
47+ ST m () [win ::: Surface]
48+
49+
50+Draw IO where
51+ Surface = State SDLSurface
52+
53+ initWindow x y = do Just srf <- lift (startSDL x y)
54+ | pure Nothing
55+ var <- new srf
56+ pure (Just var)
57+
58+ closeWindow win = do lift endSDL
59+ delete win
60+
61+ flip win = do srf <- read win
62+ lift (flipBuffers srf)
63+ poll = lift pollEvent
64+
65+ filledRectangle win (x, y) (ex, ey) (MkCol r g b a)
66+ = do srf <- read win
67+ lift $ filledRect srf x y ex ey r g b a
68+ drawLine win (x, y) (ex, ey) (MkCol r g b a)
69+ = do srf <- read win
70+ lift $ drawLine srf x y ex ey r g b a
71+
72+render : Draw m => (win : Var) -> ST m () [win ::: Surface {m}]
73+render win = do filledRectangle win (0,0) (640,480) black
74+ drawLine win (100,100) (200,200) red
75+ flip win
76+
77+loop : Draw m => (win : Var) -> ST m () [win ::: Surface {m}]
78+loop win = do render win
79+ Just AppQuit <- poll
80+ | _ => loop win
81+ pure ()
82+
83+drawMain : (ConsoleIO m, Draw m) => ST m () []
84+drawMain = do Just win <- initWindow 640 480
85+ | Nothing => putStrLn "Can't open window"
86+ loop win
87+ closeWindow win
88+
samples/ST/Graphics/Turtle.idrnew file+116
@@ -0,0 +1,116 @@
1+module Main
2+
3+import Control.ST
4+import Control.ST.ImplicitCall
5+import Draw
6+
7+interface TurtleGraphics (m : Type -> Type) where
8+ Turtle : Type
9+
10+ start : Int -> Int -> ST m (Maybe Var) [addIfJust Turtle]
11+ end : (t : Var) -> ST m () [Remove t Turtle]
12+
13+ fd : (t : Var) -> Int -> ST m () [t ::: Turtle]
14+ rt : (t : Var) -> Int -> ST m () [t ::: Turtle]
15+
16+ penup : (t : Var) -> ST m () [t ::: Turtle]
17+ pendown : (t : Var) -> ST m () [t ::: Turtle]
18+ col : (t : Var) -> Col -> ST m () [t ::: Turtle]
19+
20+ -- Render the picture drawn so far in a window, and wait for a key press
21+ render : (t : Var) -> ST m () [t ::: Turtle]
22+
23+Line : Type
24+Line = ((Int, Int), (Int, Int), Col)
25+
26+-- Implement turtle graphics in terms of existing stateful systems;
27+-- 'Draw' provides a Surface to draw on, and three states.
28+Draw m => TurtleGraphics m where
29+ Turtle = Composite [Surface {m}, -- surface to draw on
30+ State Col, -- pen colour
31+ State (Int, Int, Int, Bool), -- pen location/direction/down
32+ State (List Line)] -- lines to draw on render
33+
34+ start x y = with ST do
35+ Just srf <- initWindow x y
36+ | Nothing => pure Nothing
37+ col <- new white
38+ pos <- new (320, 200, 0, True)
39+ lines <- new []
40+ turtle <- new ()
41+ combine turtle [srf, col, pos, lines]
42+ pure (Just turtle)
43+
44+ end t = do [srf, col, pos, lines] <- split t
45+ closeWindow srf; delete col; delete pos; delete lines; delete t
46+
47+ fd t dist = with ST do
48+ [srf, col, pos, lines] <- split t
49+ (x, y, d, p) <- read pos
50+ let x' = cast x + cast dist * sin (rad d)
51+ let y' = cast y + cast dist * cos (rad d)
52+ c <- read col
53+ ls <- read lines
54+ write lines (if p then ((x, y), (cast x', cast y'), c) :: ls
55+ else ls)
56+ write pos (cast x', cast y', d, p)
57+ combine t [srf, col, pos, lines]
58+ where rad : Int -> Double
59+ rad x = (cast x * pi) / 180.0
60+
61+ rt t angle = with ST do
62+ [srf, col, pos, lines] <- split t
63+ (x, y, d, p) <- read pos
64+ write pos (x, y, d + angle `mod` 360, p)
65+ combine t [srf, col, pos, lines]
66+
67+ penup t = with ST do
68+ [srf, col, pos, lines] <- split t
69+ (x, y, d, _) <- read pos
70+ write pos (x, y, d, False)
71+ combine t [srf, col, pos, lines]
72+ pendown t = with ST do
73+ [srf, col, pos, lines] <- split t
74+ (x, y, d, _) <- read pos
75+ write pos (x, y, d, True)
76+ combine t [srf, col, pos, lines]
77+ col t c = with ST do
78+ [srf, col, pos, lines] <- split t
79+ write col c
80+ combine t [srf, col, pos, lines]
81+
82+ render t = with ST do
83+ [srf, col, pos, lines] <- split t
84+ filledRectangle srf (0, 0) (640, 480) black
85+ drawAll srf !(read lines)
86+ flip srf
87+ combine t [srf, col, pos, lines]
88+ Just ev <- poll
89+ | Nothing => render t
90+ case ev of
91+ KeyUp _ => pure ()
92+ _ => render t
93+ where drawAll : (srf : Var) -> List Line -> ST m () [srf ::: Surface {m}]
94+ drawAll srf [] = pure ()
95+ drawAll srf ((start, end, col) :: xs)
96+ = do drawLine srf start end col
97+ drawAll srf xs
98+
99+turtle : (ConsoleIO m, TurtleGraphics m) => ST m () []
100+turtle = with ST do
101+ Just t <- start 640 480
102+ | Nothing => putStr "Can't make turtle\n"
103+ col t yellow
104+ fd t 100; rt t 90
105+ col t green
106+ fd t 100; rt t 90
107+ col t red
108+ fd t 100; rt t 90
109+ col t blue
110+ fd t 100; rt t 90
111+ render t
112+ end t
113+
114+main : IO ()
115+main = run turtle
116+
samples/ST/Intro.idrnew file+75
@@ -0,0 +1,75 @@
1+import Control.ST
2+import Data.Vect
3+
4+-- Basic state
5+
6+-- increment : (x : Var) -> STrans m () [x ::: State Int]
7+-- (const [x ::: State Int])
8+increment : (x : Var) -> ST m () [x ::: State Int]
9+increment x = do num <- read x
10+ write x (num + 1)
11+
12+-- makeAndIncrement : Int -> STrans m Int [] (const [])
13+makeAndIncrement : Int -> ST m Int []
14+makeAndIncrement init = do var <- new init
15+ increment var
16+ x <- read var
17+ delete var
18+ pure x
19+
20+ioMakeAndIncrementBad : STrans IO () [] (const [])
21+ioMakeAndIncrementBad
22+ = do lift $ putStr "Enter a number: "
23+ init <- lift $ getLine
24+ var <- new (cast init)
25+ lift $ putStrLn ("var = " ++ show !(read var))
26+ increment var
27+ lift $ putStrLn ("var = " ++ show !(read var))
28+ delete var
29+
30+ioMakeAndIncrement : ConsoleIO io => STrans io () [] (const [])
31+ioMakeAndIncrement
32+ = do putStr "Enter a number: "
33+ init <- getStr
34+ var <- new (cast init)
35+ putStrLn ("var = " ++ show !(read var))
36+ increment var
37+ putStrLn ("var = " ++ show !(read var))
38+ delete var
39+
40+-- Dependent state
41+
42+-- addElement : (vec : Var) -> (item : a) ->
43+-- STrans m () [vec ::: State (Vect n a)]
44+-- (const [vec ::: State (Vect (S n) a)])
45+addElement : (vec : Var) -> (item : a) ->
46+ ST m () [vec ::: State (Vect n a) :-> State (Vect (S n) a)]
47+addElement vec item = do xs <- read vec
48+ write vec (item :: xs)
49+
50+addElement' : (vec : Var) -> (item : a) ->
51+ STrans m () [vec ::: State (Vect n a)]
52+ (const [vec ::: State (Vect (S n) a)])
53+addElement' vec item = update vec (item ::)
54+
55+-- readAndAdd : ConsoleIO io => (vec : Var) ->
56+-- STrans io Bool [vec ::: State (Vect n Integer)]
57+-- (\res => [vec ::: State (if res then Vect (S n) Integer
58+-- else Vect n Integer)])
59+readAndAdd : ConsoleIO io => (vec : Var) ->
60+ ST io Bool
61+ [vec ::: State (Vect n Integer) :->
62+ \res => State (if res then Vect (S n) Integer
63+ else Vect n Integer)]
64+readAndAdd vec = do putStr "Enter a number: "
65+ num <- getStr
66+ if all isDigit (unpack num)
67+ then returning True (update vec ((cast num) ::))
68+ else returning False (pure ())
69+
70+-- newState : STrans m Var [] (\lbl => [lbl ::: State Int])
71+newState : ST m Var [add (State Int)]
72+
73+-- removeState : (lbl : Var) -> STrans m () [lbl ::: State Int] (const [])
74+removeState : (lbl : Var) -> ST m () [remove lbl (State Int)]
75+
samples/ST/Login.idrnew file+55
@@ -0,0 +1,55 @@
1+import Control.ST
2+
3+data Access = LoggedOut | LoggedIn
4+data LoginResult = OK | BadPassword
5+
6+interface DataStore (m : Type -> Type) where
7+ data Store : Access -> Type
8+
9+ connect : ST m Var [add (Store LoggedOut)]
10+ disconnect : (store : Var) -> ST m () [remove store (Store LoggedOut)]
11+
12+ readSecret : (store : Var) -> ST m String [store ::: Store LoggedIn]
13+ login : (store : Var) ->
14+ ST m LoginResult [store ::: Store LoggedOut :->
15+ (\res => Store (case res of
16+ OK => LoggedIn
17+ BadPassword => LoggedOut))]
18+ logout : (store : Var) ->
19+ ST m () [store ::: Store LoggedIn :-> Store LoggedOut]
20+
21+getData : (ConsoleIO m, DataStore m) => ST m () []
22+getData = do st <- connect
23+ OK <- login st
24+ | BadPassword => do putStrLn "Failure"
25+ disconnect st
26+ secret <- readSecret st
27+ putStrLn ("Secret is: " ++ show secret)
28+ logout st
29+ disconnect st
30+
31+-- badGet : DataStore m => ST m () []
32+-- badGet = do st <- connect
33+-- secret <- readSecret st
34+-- disconnect st
35+
36+DataStore IO where
37+ Store x = State String -- represents secret data
38+
39+ connect = do store <- new "Secret Data"
40+ pure store
41+
42+ disconnect store = delete store
43+
44+ readSecret store = readSecret store
45+
46+ login store = do putStr "Enter password: "
47+ p <- getStr
48+ if p == "Mornington Crescent"
49+ then pure OK
50+ else pure BadPassword
51+ logout store = pure ()
52+
53+main : IO ()
54+main = run getData
55+
samples/ST/LoginCount.idrnew file+77
@@ -0,0 +1,77 @@
1+import Control.ST
2+import Control.ST.ImplicitCall
3+
4+data Access = LoggedOut | LoggedIn
5+data LoginResult = OK | BadPassword
6+
7+interface DataStore (m : Type -> Type) where
8+ data Store : Access -> Type
9+
10+ connect : ST m Var [add (Store LoggedOut)]
11+ disconnect : (store : Var) -> ST m () [remove store (Store LoggedOut)]
12+
13+ readSecret : (store : Var) -> ST m String [store ::: Store LoggedIn]
14+ login : (store : Var) ->
15+ ST m LoginResult [store ::: Store LoggedOut :->
16+ (\res => Store (case res of
17+ OK => LoggedIn
18+ BadPassword => LoggedOut))]
19+ logout : (store : Var) ->
20+ ST m () [store ::: Store LoggedIn :-> Store LoggedOut]
21+
22+getData : (ConsoleIO m, DataStore m) =>
23+ (failcount : Var) -> ST m () [failcount ::: State Integer]
24+getData failcount
25+ = do st <- call connect
26+ OK <- login st
27+ | BadPassword => do putStrLn "Failure"
28+ fc <- read failcount
29+ write failcount (fc + 1)
30+ putStrLn ("Number of failures: " ++ show (fc + 1))
31+ disconnect st
32+ getData failcount
33+ secret <- readSecret st
34+ putStrLn ("Secret is: " ++ show secret)
35+ logout st
36+ disconnect st
37+ getData failcount
38+
39+getData2 : (ConsoleIO m, DataStore m) =>
40+ (st, failcount : Var) ->
41+ ST m () [st ::: Store {m} LoggedOut, failcount ::: State Integer]
42+getData2 st failcount
43+ = do OK <- login st
44+ | BadPassword => do putStrLn "Failure"
45+ fc <- read failcount
46+ write failcount (fc + 1)
47+ putStrLn ("Number of failures: " ++ show (fc + 1))
48+ getData2 st failcount
49+ secret <- readSecret st
50+ putStrLn ("Secret is: " ++ show secret)
51+ logout st
52+ getData2 st failcount
53+
54+DataStore IO where
55+ Store x = State String -- represents secret data
56+
57+ connect = do store <- new "Secret Data"
58+ pure store
59+
60+ disconnect store = delete store
61+
62+ readSecret store = read store
63+
64+ login store = do putStr "Enter password: "
65+ p <- getStr
66+ if p == "Mornington Crescent"
67+ then pure OK
68+ else pure BadPassword
69+ logout store = pure ()
70+
71+main : IO ()
72+main = run (do fc <- new 0
73+ st <- connect
74+ getData2 st fc
75+ disconnect st
76+ delete fc)
77+
samples/ST/Net/EchoSimple.idrnew file+27
@@ -0,0 +1,27 @@
1+import Network.Socket
2+import Network
3+import Control.ST
4+import Control.ST.ImplicitCall
5+
6+echoServer : (ConsoleIO m, Sockets m) => (sock : Var) ->
7+ ST m () [Remove sock (Sock {m} Listening)]
8+echoServer sock =
9+ do Right new <- accept sock
10+ | Left err => do close sock; remove sock
11+ Right msg <- recv new
12+ | Left err => do close sock; remove sock; remove new
13+ putStr (msg ++ "\n")
14+ Right ok <- send new ("You said " ++ msg)
15+ | Left err => do remove new; close sock; remove sock
16+ close new; remove new; echoServer sock
17+
18+startServer : (ConsoleIO m, Sockets m) => ST m () []
19+startServer =
20+ do Right sock <- socket Stream | Left err => pure ()
21+ Right ok <- bind sock Nothing 9442 | Left err => remove sock
22+ Right ok <- listen sock | Left err => remove sock
23+ echoServer sock
24+
25+main : IO ()
26+main = run startServer
27+
samples/ST/Net/Network.idrnew file+107
@@ -0,0 +1,107 @@
1+module Network
2+
3+import Control.ST
4+import Network.Socket
5+
6+public export
7+data SocketState = Ready
8+ | Bound
9+ | Listening
10+ | Open
11+ | Closed
12+
13+public export
14+data CloseOK : SocketState -> Type where
15+ CloseOpen : CloseOK Open
16+ CloseListening : CloseOK Listening
17+
18+-- Sockets API. By convention, the methods return 'Left' on failure or
19+-- 'Right' on success (even if the error/result is merely unit).
20+public export
21+interface Sockets (m : Type -> Type) where
22+ Sock : SocketState -> Type
23+
24+ -- Create a new socket. If successful, it's in the Closed state
25+ socket : SocketType ->
26+ ST m (Either () Var) [addIfRight (Sock Ready)]
27+
28+ -- Bind a socket to a port. If successful, it's moved to the Bound state.
29+ bind : (sock : Var) -> (addr : Maybe SocketAddress) -> (port : Port) ->
30+ ST m (Either () ())
31+ [sock ::: Sock Ready :-> (Sock Closed `or` Sock Bound)]
32+ -- Listen for connections on a socket. If successful, it's moved to the
33+ -- Listening state
34+ listen : (sock : Var) ->
35+ ST m (Either () ())
36+ [sock ::: Sock Bound :-> (Sock Closed `or` Sock Listening)]
37+ -- Accept an incoming connection on a Listening socket. If successful,
38+ -- creates a new socket in the Open Server state, and keeps the existing
39+ -- socket in the Listening state
40+ accept : (sock : Var) ->
41+ ST m (Either () Var)
42+ [sock ::: Sock Listening, addIfRight (Sock Open)]
43+
44+ -- Connect to a remote address on a socket. If successful, moves to the
45+ -- Open Client state
46+ connect : (sock : Var) -> SocketAddress -> Port ->
47+ ST m (Either () ())
48+ [sock ::: Sock Ready :-> (Sock Closed `or` Sock Open)]
49+
50+ -- Close an Open or Listening socket
51+ close : (sock : Var) ->
52+ {auto prf : CloseOK st} ->
53+ ST m () [sock ::: Sock st :-> Sock Closed]
54+
55+ remove : (sock : Var) ->
56+ ST m () [Remove sock (Sock Closed)]
57+
58+ -- Send a message on a connected socket.
59+ -- On failure, move the socket to the Closed state
60+ send : (sock : Var) -> String ->
61+ ST m (Either () ())
62+ [sock ::: Sock Open :-> (Sock Closed `or` Sock Open)]
63+ -- Receive a message on a connected socket
64+ -- On failure, move the socket to the Closed state
65+ recv : (sock : Var) ->
66+ ST m (Either () String)
67+ [sock ::: Sock Open :-> (Sock Closed `or` Sock Open)]
68+
69+export
70+implementation Sockets IO where
71+ Sock _ = State Socket
72+
73+ socket ty = do Right sock <- lift $ Socket.socket AF_INET ty 0
74+ | Left err => pure (Left ())
75+ lbl <- new sock
76+ pure (Right lbl)
77+
78+ bind sock addr port = do ok <- lift $ bind !(read sock) addr port
79+ if ok /= 0
80+ then pure (Left ())
81+ else pure (Right ())
82+ listen sock = do ok <- lift $ listen !(read sock)
83+ if ok /= 0
84+ then pure (Left ())
85+ else pure (Right ())
86+ accept sock = do Right (conn, addr) <- lift $ accept !(read sock)
87+ | Left err => pure (Left ())
88+ lbl <- new conn
89+ toEnd lbl
90+ pure (Right lbl)
91+
92+ connect sock addr port
93+ = do ok <- lift $ connect !(read sock) addr port
94+ if ok /= 0
95+ then pure (Left ())
96+ else pure (Right ())
97+ close sock = do lift $ close !(read sock)
98+ pure ()
99+ remove sock = delete sock
100+
101+ send sock msg = do Right _ <- lift $ send !(read sock) msg
102+ | Left _ => pure (Left ())
103+ pure (Right ())
104+ recv sock = do Right (msg, len) <- lift $ recv !(read sock) 1024 -- Yes, yes...
105+ | Left _ => pure (Left ())
106+ pure (Right msg)
107+
samples/ST/Net/RandServer.idrnew file+156
@@ -0,0 +1,156 @@
1+import Network.Socket
2+import Control.ST
3+import Control.ST.ImplicitCall
4+import System
5+
6+import Network
7+import Threads
8+
9+{- A random number server.
10+
11+This receives requests from a client, as a number, and sends a reply
12+which is a random number within the requested bound.
13+
14+There are two states: one for the server, and one for a connected session.
15+The server repeatedly listens for requests and creats a session for each
16+incoming request.
17+-}
18+
19+-- States of a connected session
20+data SessionState = Waiting -- waiting for the client to send
21+ | Processing -- calculating a response to send back
22+ | Done -- received message and replied to it
23+
24+interface RandomSession (m : Type -> Type) where
25+ -- A connected session
26+ Connection : SessionState -> Type
27+ -- A server listening for connections
28+ Server : Type
29+
30+ -- Receive a request on a Waiting connection. If there is a request
31+ -- available, move to the Processing state
32+ recvReq : (conn : Var) ->
33+ ST m (Maybe Integer)
34+ [conn ::: Connection Waiting :->
35+ \res => Connection (case res of
36+ Nothing => Done
37+ Just _ => Processing)]
38+ -- Send a reply, and move the connection to the Done state
39+ sendResp : (conn : Var) -> Integer ->
40+ ST m () [conn ::: Connection Processing :-> Connection Done]
41+
42+ -- Create a server
43+ start : ST m (Maybe Var) [addIfJust Server]
44+ -- Close a server
45+ quit : (srv : Var) -> ST m () [Remove srv Server]
46+ -- Finish a connection
47+ done : (conn : Var) -> ST m () [Remove conn (Connection Done)]
48+
49+ -- Listen for an incoming connection. If there is one, create a session
50+ -- with a connection in the Waiting state
51+ accept : (srv : Var) ->
52+ ST m (Maybe Var)
53+ [srv ::: Server, addIfJust (Connection Waiting)]
54+
55+interface Sleep (m : Type -> Type) where
56+ usleep : (i : Int) -> { auto prf : So (i >= 0 && i <= 1000000) } ->
57+ STrans m () xs (const xs)
58+
59+Sleep IO where
60+ usleep x = lift (System.usleep x)
61+
62+
63+using (Sleep io, ConsoleIO io, RandomSession io, Conc io)
64+ rndSession : (conn : Var) ->
65+ ST io () [Remove conn (Connection {m=io} Waiting)]
66+ rndSession conn =
67+ do Just bound <- call (recvReq conn)
68+ | Nothing => do putStr "Nothing received\n"
69+ call (done conn)
70+ putStr "Calculating reply...\n"
71+ usleep 1000000
72+ sendResp conn bound -- (seed `mod` (bound + 1))
73+ call (done conn)
74+
75+ rndLoop : (srv : Var) -> ST io () [srv ::: Server {m=io}]
76+ rndLoop srv
77+ = do Just conn <- accept srv
78+ | Nothing => putStr "accept failed\n"
79+ putStr "Connection received\n"
80+ fork (rndSession conn)
81+ rndLoop srv
82+
83+ rndServer : ST io () []
84+ rndServer
85+ = do Just srv <- start
86+ | Nothing => putStr "Can't start server\n"
87+ call (rndLoop srv)
88+ quit srv
89+
90+implementation (ConsoleIO io, Sockets io) => RandomSession io where
91+
92+ -- Connections and servers are composite states, so to implement things
93+ -- in terms of them we need to 'split' at the state and 'combine' at the
94+ -- end, in every method
95+ Connection Waiting = Composite [State Integer, Sock {m=io} Open]
96+ Connection Processing = Composite [State Integer, Sock {m=io} Open]
97+ Connection Done = Composite [State Integer, Sock {m=io} Closed]
98+
99+ Server = Composite [State Integer, Sock {m=io} Listening]
100+
101+ recvReq rec = do [seed, conn] <- split rec
102+ Right msg <- recv conn
103+ | Left err => do combine rec [seed, conn]
104+ pure Nothing
105+ putStr ("Incoming " ++ show msg ++ "\n")
106+ combine rec [seed, conn]
107+ pure (Just (cast msg))
108+
109+ sendResp rec val = do [seed, conn] <- split rec
110+ Right () <- send conn (cast (!(read seed) `mod` val) ++ "\n")
111+ | Left err => do combine rec [seed, conn]
112+ pure ()
113+ close conn
114+ combine rec [seed, conn]
115+
116+ start = do srv <- new ()
117+ Right sock <- socket Stream
118+ | Left err => do delete srv; pure Nothing
119+ Right () <- bind sock Nothing 9442
120+ | Left err => do call (remove sock)
121+ delete srv
122+ pure Nothing
123+ Right () <- listen sock
124+ | Left err => do call (remove sock)
125+ delete srv
126+ pure Nothing
127+ putStr "Started server\n"
128+ seed <- new 12345
129+ combine srv [seed, sock]
130+ pure (Just srv)
131+
132+ quit srv = do [seed, sock] <- split srv -- need to delete everything
133+ close sock; remove sock; delete seed; delete srv
134+ done conn = do [seed, sock] <- split conn -- need to delete connection data
135+ remove sock; delete seed; delete conn
136+
137+ accept srv = do [seed, sock] <- split srv
138+ seedVal <- read seed
139+ write seed ((1664525 * seedVal + 1013904223)
140+ `prim__sremBigInt` (pow 2 32))
141+ Right conn <- accept sock
142+ | Left err => do combine srv [seed, sock]
143+ pure Nothing -- no incoming message
144+ -- We're sending the seed to the child process and keeping
145+ -- a copy ourselves, so we need to explicitly make a new
146+ -- one
147+ rec <- new ()
148+ seed' <- new seedVal
149+ combine rec [seed', conn]
150+ combine srv [seed, sock]
151+ toEnd rec
152+ pure (Just rec)
153+
154+main : IO ()
155+main = run rndServer
156+
samples/ST/Net/Threads.idrnew file+23
@@ -0,0 +1,23 @@
1+module Threads
2+
3+import Control.ST
4+import System.Concurrency.Channels
5+import System
6+
7+public export
8+interface Conc (m : Type -> Type) where
9+ -- 'Fork' sends some resources to the spawned thread, and keeps the rest
10+ -- for the parent
11+ -- TODO: Note that there is nothing here yet about how the threads
12+ -- communicate with each other...
13+ fork : (thread : STrans m () thread_res (const [])) ->
14+ {auto tprf : SubCtxt thread_res all} ->
15+ STrans m () all (const (kept tprf))
16+
17+export
18+implementation Conc IO where
19+ fork thread
20+ = do threadEnv <- dropSubCtxt
21+ lift $ spawn (do runWith threadEnv thread
22+ pure ())
23+ pure ()
samples/ST/TreeTag.idrnew file+29
@@ -0,0 +1,29 @@
1+import Control.ST
2+
3+data BTree a = Leaf
4+ | Node (BTree a) a (BTree a)
5+
6+testTree : BTree String
7+testTree = Node (Node Leaf "Jim" Leaf)
8+ "Fred"
9+ (Node (Node Leaf "Alice" Leaf)
10+ "Sheila"
11+ (Node Leaf "Bob" Leaf))
12+
13+
14+treeTagAux : (tag : Var) -> BTree a -> ST m (BTree (Int, a)) [tag ::: State Int]
15+treeTagAux tag Leaf = pure Leaf
16+treeTagAux tag (Node left val right)
17+ = do left' <- treeTagAux tag left
18+ thisTag <- read tag
19+ write tag (thisTag + 1)
20+ right' <- treeTagAux tag right
21+ pure (Node left' (thisTag, val) right')
22+
23+treeTag : (i : Int) -> BTree a -> BTree (Int, a)
24+treeTag i tree = runPure (do tag <- new i
25+ t <- treeTagAux tag tree
26+ delete tag
27+ pure t)
28+
29+