Hacker News new | ask | show | jobs
by nicolodavis 546 days ago
I agree that asynchronous flows are hard to represent using a state object.

I chose to go with a monadic approach in boardgamelab.app (I'm not limited by language features since I'm designing it from the ground up for this use case). The language is pure and functional with managed effects. You can express things like:

  set of cards -> filter by action cards -> choose -> [card]
  do stuff with [card]
written in a synchronous style, while under the hood it:

1. suspends the rule.

2. waits for the user to make a choice.

3. resumes the rule with the choice made by the user.

(note: listed above is a simplified text representation. The Boardgame Lab structure editor uses a block-based visual language.)

If written in Haskell, the underlying monad would look something like this:

  {-# LANGUAGE ExistentialQuantification #-}

  newtype Rule a = Rule {fn :: State -> (RuleResult a, State)}

  data RuleResult a
    = Done a
    | forall x. Show x => Choose [x] (x -> Rule a)

  instance Monad Rule where
    m >>= k = Rule $ \state ->
      case fn m state of
        (Done a, state') -> fn (k a) state'
        (Choose list m', state') -> (Choose list (m' >=> k), state')
i.e. each rule returns either a completed result or a choice along with a continuation of the remainder of the rule past that choice.