Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating a DSL #24

Open
lboklin opened this issue Jun 24, 2019 · 3 comments
Open

Creating a DSL #24

lboklin opened this issue Jun 24, 2019 · 3 comments

Comments

@lboklin
Copy link
Collaborator

lboklin commented Jun 24, 2019

The currently pending PR (#23) requires the user to make use of Template Haskell, which made me think that if we're going to introduce code generation we may as well go all the way and create a DSL that is at least ergonomic to use and closer to GDScript in syntax.

I doodled some pseudo code for what we might want it to end up looking like:

-- language exts ...
-- module ...
-- import ...
extends @RigidBody2D $ Mob
  { minSpeed = 150
  , maxSpeed = 250
  , mobTypes = ["walk", "swim", "fly"]
  }
data Mob = Mob
  { minSpeed :: Float
  , maxSpeed :: Float
  , mobTypes :: [Text]
  }


_ready :: RigidBody2D -> Mob -> IO Mob
_ready self mobState = do
  let randElem xs = (xs !!) <$> randomRIO (0, length xs - 1)
  randAnim <- mobTypes mobState & randElem >>= toLowLevel
  getNode @AnimatedSprite self "AnimatedSprite" >>= (`set_animation` randAnim)


_on_VisibilityNotifier2D_screen_exited :: RigidBody2D -> Mob -> IO Mob
_on_VisibilityNotifier2D_screen_exited self _ = do
  queue_free self

this would generate loosely something like this:

-- language exts ...
-- module ...
-- import ...

-- `extends ...` Generates a wrapper type, a HasBaseClass and a NativeScript
--  instance which might look like this
data MobWrapper = MobW RigidBody2D (MVar Mob)

instance HasBaseClass MobWrapper where
  type BaseClass MobWrapper = RigidBody2D
  super (MobW base _) = base

instance NativeScript MobWrapper where
  classInit base = MobWrapper base <$> newMVar
    { minSpeed = 150
    , maxSpeed = 250
    , mobTypes = ["walk", "swim", "fly"]
    }
  classMethods =
    -- All the top-level function declarations are turned into this
    [ method0 "_ready" $ \(MobWrapper base mva) -> do
        a <- readMVar mva
        _ready base a
    , method0 "_on_VisibilityNotifier2D_screen_exited" $ \(MobWrapper base mva) -> do
        a <- readMVar mva
        _on_VisibilityNotifier2D_screen_exited base a
    ]

data Mob = Mob
  { minSpeed :: Float
  , maxSpeed :: Float
  , mobTypes :: [Text]
  }

_ready :: RigidBody2D -> Mob -> IO Mob
_ready self mobState = do
  let randElem xs = (xs !!) <$> randomRIO (0, length xs - 1)
  randAnim <- mobTypes mobState & randElem >>= toLowLevel
  getNode @AnimatedSprite self "AnimatedSprite" >>= (`set_animation` randAnim)

_on_VisibilityNotifier2D_screen_exited :: RigidBody2D -> Mob -> IO Mob
_on_VisibilityNotifier2D_screen_exited self _ = do
  queue_free self

This could probably be made even tidier with a state transformer type.

The result is much closer to a functional substitute for GDScript, but whether this works in implementation remains to be seen, as I haven't spent enough time with Haskell code generation to spot any blockers but handling variable number of arguments for various top level function declarations might be difficult (or not).

It may be worth investigating available extensible records libraries as well.

@rainbyte
Copy link
Contributor

rainbyte commented Jul 5, 2019

Let me see if I understand... Every method would be a function which would have a type similar to the following one?

foo :: Monad m => b -> c -> a -> m a 

@lboklin
Copy link
Collaborator Author

lboklin commented Jul 5, 2019

@rainbyte They already do since we usually need to do IO in them; the only changes this makes to type signatures is that we

  • separate our state-keeping data type from the class we inherit (so Mob and RigidBody2D are passed separately) - I don't know if this is the best way to do it or if they should be joined with a data Godot a = Godot Object a higher order type so we receive a Godot Mob, though we're running out of good type names at this point :p
  • We get to update our state without ever touching MVars or TVars becauses that happens before and after we see them

@YellowOnion
Copy link

Is there any update for this? plus what about Godot monad abstracting over Monad.IO.Class?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants