on master help sign in
Add section on composing state machines
a674540Edwin Brady
docs/st/composing.rst+475−1
@@ -295,6 +295,480 @@ the overall resources.
295Composite resources: Hierarchies of state machines
295Composite resources: Hierarchies of state machines
296==================================================
296==================================================
297
297
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:
299
338
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
300
759
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:
56.. code-block:: idris
56.. code-block:: idris
57
57
58 interface DataStore (m : Type -> Type) where
58 interface DataStore (m : Type -> Type) where
59− data Store : Access -> Type
59+ 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.
71
60
72We can continue to populate this interface with operations on the store. Among
61We can continue to populate this interface with operations on the store. Among
73other advantages, by separating the *interface* from its *implementation* we
62other advantages, by separating the *interface* from its *implementation* we
@@ -213,7 +202,7 @@ This completes the interface, repeated in full for reference below:
213.. code-block:: idris
202.. code-block:: idris
214
203
215 interface DataStore (m : Type -> Type) where
204 interface DataStore (m : Type -> Type) where
216− data Store : Access -> Type
205+ Store : Access -> Type
217
206
218 connect : ST m Var [add (Store LoggedOut)]
207 connect : ST m Var [add (Store LoggedOut)]
219 disconnect : (store : Var) -> ST m () [remove store (Store LoggedOut)]
208 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+