packages feed

bluefin 0.2.2.0 → 0.2.3.0

raw patch · 4 files changed

+351/−4 lines, 4 filesdep ~bluefin-internalPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: bluefin-internal

API changes (from Hackage documentation)

- Bluefin.Compound: data OneWayCoercibleHandle (a :: k -> Type) (es :: k)
+ Bluefin.Compound: MkOneWayCoercibleHandle :: a es -> OneWayCoercibleHandle (a :: k -> Type) (es :: k)
+ Bluefin.Compound: newtype OneWayCoercibleHandle (a :: k -> Type) (es :: k)
+ Bluefin.Compound: oneWayCoercibleImpl :: OneWayCoercible a b => OneWayCoercibleD a b
+ Bluefin.DslBuilder: data DslBuilder (h :: Effects -> Type) r
+ Bluefin.DslBuilder: dslBuilder :: (forall (e :: Effects). () => h e -> Eff e r) -> DslBuilder h r
+ Bluefin.DslBuilder: runDslBuilder :: forall h (es :: Effects) r. Handle h => h es -> DslBuilder h r -> Eff es r

Files

CHANGELOG.md view
@@ -1,3 +1,7 @@+# 0.2.1.0++* Add `Bluefin.DslBuilder`+ ## 0.2.2.0  * `Bluefin.Compound`: add `OneWayCoercible`, `OneWayCoercibleHandle`,
bluefin.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               bluefin-version:            0.2.2.0+version:            0.2.3.0 license:            MIT license-file:       LICENSE author:             Tom Ellis@@ -24,6 +24,7 @@       Bluefin.Compound,       Bluefin.Consume,       Bluefin.Coroutine,+      Bluefin.DslBuilder,       Bluefin.EarlyReturn,       Bluefin.Eff,       Bluefin.Exception,@@ -39,6 +40,6 @@       Bluefin.System.IO,       Bluefin.Writer,     build-depends:-      bluefin-internal >= 0.3.0.0 && < 0.4+      bluefin-internal >= 0.3.1.0 && < 0.4     hs-source-dirs:   src     default-language: Haskell2010
src/Bluefin/Compound.hs view
@@ -661,8 +661,8 @@      -- | The documentation for 'Handle' shows how to use     -- @OneWayCoercible@ to define @Handle@ instances.-    OneWayCoercible.OneWayCoercible,-    OneWayCoercibleHandle,+    OneWayCoercible.OneWayCoercible(OneWayCoercible.oneWayCoercibleImpl),+    OneWayCoercibleHandle(MkOneWayCoercibleHandle),     OneWayCoercible.gOneWayCoercible,     OneWayCoercible.Generic, 
+ src/Bluefin/DslBuilder.hs view
@@ -0,0 +1,342 @@+module Bluefin.DslBuilder+  ( -- | Haksell is great for writing domain specific languages (DSLs)+    -- and @Bluefin.DslBuilder@ provides an easy way to write DSLs+    -- using Bluefin.++    -- * Robot arena example++    -- ** Data types for the robot arena++    -- | Here's an example of the use of @Bluefin.DslBuilder@. Suppose+    -- we have a data type that represents the location of robots and+    -- obstacles in a two-dimensional square arena:+    --+    -- @+    -- data Arena = MkArena+    --  { arenaRobots :: [RobotEntry],+    --    arenaObstacles :: [ObstacleEntry]+    --  }+    -- @+    --+    -- Each @RobotEntry@ stores the robot's name, coordinates on the+    -- 2d grid, facing direction, and instructions for robot to carry+    -- out+    --+    -- @+    -- type RobotEntry = (String, (Int, Int), Direction, [Instruction])+    -- @+    --+    -- The instructions that a robot can perform are to wait for a+    -- given number of time units, move forward, turn left and turn+    -- right.+    --+    -- @+    -- data Instruction = Wait Int | Forward | TurnLeft | TurnRight+    -- data Direction = N | E | S | W+    -- @+    --+    -- The @ObstacleEntry@s store the type of each obstacle and its+    -- coordinates on the 2d grid.+    --+    -- @+    -- type ObstacleEntry = (Obstacle, (Int, Int))+    --+    -- data Obstacle = Sand | Rock | Iron+    -- @++    -- ** Defining an @Arena@ by hand++    -- | Suppose we want an @Arena@ with a red robot which moves+    -- towards a sand obstacle, and a blue robot that is stuck behind+    -- iron obstacles and can only turn around on the spot. Here's an+    -- ASCII diagram of the initial position we want, where @r@ is the+    -- red robot, @S@ is a sand obstacle, @b@ is the blue robot and+    -- @I@ is an iron obstacle:+    --+    -- @+    -- 5|+    -- 4|  III+    -- 3|  IbI+    -- 2|  III+    -- 1|+    -- 0|r    S+    --  +------+    --   012345+    -- @+    --+    -- We can define such an @Arena@ by hand like this:+    --+    -- @+    -- myArena :: Arena+    -- myArena =+    --   MkArena+    --     { arenaRobots =+    --         [ ("red", (0, 0), E, [Wait 100, Forward, Forward, Forward, Forward]),+    --           ("blue", (3, 3), N, [TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight, TurnRight])+    --         ],+    --       arenaObstacles = [(Sand, (5, 0)), (Iron, (2, 2)), (Iron, (2, 3)), (Iron, (2, 4)), (Iron, (3, 2)), (Iron, (3, 4)), (Iron, (4, 2)), (Iron, (4, 3)), (Iron, (4, 4))]+    --     }+    -- @+    --+    -- That's messy and contains a lot of repetition!  We can use+    -- Haskell constructs to do a bit better:+    --+    -- @+    -- myArena2 :: Arena+    -- myArena2 =+    --   MkArena+    --     { arenaRobots =+    --         [ ("red", (0, 0), E, Wait 100 : replicate 4 Forward),+    --           ("blue", (3, 3), N, replicate 20 TurnRight)+    --         ],+    --       arenaObstacles =+    --         (Sand, (5, 0))+    --           : [ (Iron, (x, y))+    --               | x <- [2 .. 4],+    --                 y <- [2 .. 4],+    --                 (x, y) /= (3, 3)+    --             ]+    --     }+    -- @+    --+    -- That's more compressed but it doesn't describe our intent+    -- clearly.++    -- ** Defining an @Arena@ with a DSL++    -- | Let's use @Bluefin.DslBuilder@ to write a DSL that allows us+    -- to express our intent more clearly.  Before we define the DSL,+    -- let's look at what it will allow us to write.  In @myDslArena@+    -- below I can define the red robot and its obstacles separately+    -- from the blue robot and its obstacles, and I can use `for_`+    -- loops to conveniently define the blue robot's iron cage.+    --+    -- @+    -- myDslArena :: Arena+    -- myDslArena = buildArena $ do+    --   -- 5|+    --   -- 4|+    --   -- 3|+    --   -- 2|+    --   -- 1|+    --   -- 0|R    S+    --   --  +------+    --   --   012345+    --   robot "red" (0, 0) E $ do+    --     wait 100+    --     forward 4+    --+    --   obstacle Sand (5, 0)+    --+    --   -- 5|+    --   -- 4|  III+    --   -- 3|  IBI+    --   -- 2|  III+    --   -- 1|+    --   -- 0|+    --   --  +------+    --   --   012345+    --   robot "blue" (3, 3) N $ do+    --     replicateM_ 10 aboutFace+    --+    --   for_ [2 .. 4] $ \\x -> do+    --     for_ [2 .. 4] $ \\y -> do+    --       unless ((x, y) == (3, 3)) $ do+    --         obstacle Iron (x, y)+    -- @++    -- ** Arena DSL definitions++    -- | So what are the definitions of the components that go into+    -- building an @Arena@?++    -- *** @buildArena@ and @ArenaBuilder@++    -- | Firstly, what does @buildArena@ do?  It's going to have this+    -- type:+    --+    -- @+    -- buildArena :: ArenaBuilder -> Arena+    -- @+    --+    -- @ArenaBuilder@ is the type of the @do@ block which contains the+    -- @robot@ and @obstacle@ entries, and is a convenience type+    -- synonym:+    --+    -- @+    -- type ArenaBuilder = ArenaBuilder_ ()+    -- @+    --+    -- @ArenaBuilder_@ is a @Monad@ and is the first component+    -- we are going to build using @Bluefin.DslBuilder@:+    --+    -- @+    -- newtype ArenaBuilder_ r+    --   = MkArenaBuilder ('DslBuilder' ArenaH r)+    --   deriving (Functor, Applicative, Monad)+    -- @+    --+    -- @DslBuilder ArenaH@ is a @Monad@ that allows us access to the+    -- effects inside the handle @ArenaH@ (and no others).  So what is+    -- @ArenaH@? It is defined like this:+    --+    -- @+    -- data ArenaH e = MkArenaH ('Bluefin.Stream.Stream' RobotEntry e) (Stream ObstacleEntry e)+    --   deriving t'Bluefin.Compound.Generic'+    --   deriving t'Bluefin.Compound.Handle' via t'Bluefin.Compound.OneWayCoercibleHandle' ArenaH+    --+    -- instance (e :> es) => 'Bluefin.Compound.OneWayCoercible' (ArenaH e) (ArenaH es) where+    --   'Bluefin.Compound.oneWayCoercibleImpl' = 'Bluefin.Compound.gOneWayCoercible'+    -- @+    --+    -- What does that mean? Well, @ArenaH@ is defined according to one+    -- of the recipes from "Bluefin.Compound", and gives the ability+    -- to yield to a @Stream@ of @RobotEntry@s and a stream of+    -- @ObstacleEntry@s, i.e. the components that make up an @Arena@.+    -- The only things we can do with the @ArenaH@ then are to give it+    -- @RobotEntry@s or @ObstacleEntry@s.  How do we get them?++    -- *** @obstacle@++    -- | To get an @ObstacleEntry@ we use the @obstacle@ command.+    -- It has type+    --+    -- @+    -- obstacle :: Strength -> (Int, Int) -> ArenaBuilder+    -- @+    --+    -- When calling @obstacle@ we specify the strength of the obstacle+    -- and its coordinates in the arena.  In fact, having those gives+    -- us exactly what we need to yield an @ObstacleEntry@ to the+    -- @Stream ObstacleEntry@ inside the @ArenaBuilder@:+    --+    -- @+    -- obstacle o coord =+    --   MkArenaBuilder $ 'dslBuilder' $ \(MkArenaH _ yobstacle) -> do+    --     'Bluefin.Stream.yield' yobstacle (o, coord)+    -- @++    -- *** @robot@ and @InstructionsBuilder@++    -- | To get a @RobotEntry@ we use the @robot@ component. It has+    -- type+    --+    -- @+    -- robot ::+    --   String -> (Int, Int) -> Direction -> InstructionsBuilder -> ArenaBuilder+    -- @+    --+    -- When calling @robot@ we specify the name, coordinates, and+    -- facing direction for our robot.  We also specify the+    -- instructions for the robot by providing an+    -- @InstructionsBuilder@? What's that?  It's /another/ @Monad@+    -- defined in terms of @DslBuilder@:+    --+    -- @+    -- type InstructionsBuilder = InstructionsBuilder_ ()+    --+    -- newtype InstructionsBuilder_ r+    --   = MkInstructionsBuilder ('DslBuilder' InstructionsH r)+    --   deriving (Functor, Applicative, Monad)+    -- @+    --+    -- Like with @ArenaBuilder@, to define the @Monad@ we define a+    -- handle, this time @InstructionsH@:+    --+    -- @+    -- data InstructionsH e = MkInstructionsH ('Bluefin.Stream.Stream' Instruction e)+    --   deriving t'Bluefin.Compound.Generic'+    --   deriving t'Bluefin.Compound.Handle' via t'Bluefin.Compound.OneWayCoercibleHandle' InstructionsH+    --+    -- instance (e :> es) => t'Bluefin.Compound.OneWayCoercible' (InstructionsH e) (InstructionsH es) where+    --   oneWayCoercibleImpl = 'Bluefin.Compound.gOneWayCoercible'+    -- @+    --+    -- @InstructionsH@ allows us to yield to a sequence of+    -- @Instruction@s, i.e. the type of robot instructions defined+    -- above and used in @Arena@ via @RobotEntry@.  In fact, the job+    -- of @robot@ is exactly to allow us to define a @RobotEntry@ and+    -- yield it to the @Stream RobotEntry@ of @Arena@:+    --+    -- @+    -- robot name coords dir (MkInstructionsBuilder ibuilder) =+    --   MkArenaBuilder $ 'dslBuilder' $ \\(MkArenaH yrobot _) -> do+    --     (insns, ()) \<- 'Bluefin.Stream.yieldToList' $ \\yinsns -> do+    --       'runDslBuilder' (MkInstructionsH ('Bluefin.Compound.mapHandle' yinsns)) ibuilder+    --+    --     'Bluefin.Stream.yield' yrobot (name, coords, dir, insns)+    -- @++    -- *** Creating @InstructionsBuilder@s++    -- | In a @do@ block of type @InstructionsBuilder@ we want to be+    -- able to write things like @wait 100@, @forward 4@ and+    -- @aboutFace@.  What are they?  To define commands of type+    -- @InstructionsBuilder@ we use this convenience function:+    --+    -- @+    -- instructionsBuilder :: Instruction -> InstructionsBuilder+    -- instructionsBuilder insn =+    --   MkInstructionsBuilder $ dslBuilder $ \\(MkInstructionsH yinsn) -> do+    --     yield yinsn insn+    -- @+    --+    -- which we can use as follows to define commands as follows:+    --+    -- @+    -- wait :: Int -> InstructionsBuilder+    -- wait n = instructionsBuilder (Wait n)+    --+    -- turnLeft :: InstructionsBuilder+    -- turnLeft = instructionsBuilder TurnLeft+    --+    -- turnRight :: InstructionsBuilder+    -- turnRight = instructionsBuilder TurnRight+    --+    -- aboutFace :: InstructionsBuilder+    -- aboutFace = do+    --   turnRight+    --   turnRight+    --+    -- forward :: Int -> InstructionsBuilder+    -- forward n = replicateM_ n (instructionsBuilder Forward)+    -- @++    -- *** Implementing @buildArena@++    -- | We're now ready to implement @buildArena@. We create a+    -- @Stream RobotEntry@ and a @Stream ObstacleEntry@ to pass to the+    -- @MkArenaH@ constructor, and use @runDslBuilder@ to run the+    -- @ArenaBuilder@ provided.  (This is a lot like some of the approaches+    -- in "Bluefin.Compound".)+    --+    -- @+    -- buildArena :: ArenaBuilder -> Arena+    -- buildArena (MkArenaBuilder arenaBuilder) = runPureEff $ do+    --   (robots, obstacles) \<- 'Bluefin.Stream.yieldToList' $ \\yrobots -> do+    --     (obstacles, ()) \<- yieldToList $ \\yobstacles -> do+    --       'runDslBuilder'+    --         (MkArenaH ('Bluefin.Compound.mapHandle' yrobots) (mapHandle yobstacles))+    --         arenaBuilder+    --+    --     pure obstacles+    --+    --   pure+    --     MkArena+    --       { arenaRobots = robots,+    --         arenaObstacles = obstacles+    --       }+    -- @+    --+    -- And that's all we need to support the implementation of+    -- @myDslArena :: Arena@ given above!++    -- * @DslBuilder@++    DslBuilder,+    dslBuilder,+    runDslBuilder,+  )+where++import Bluefin.Internal.DslBuilder