Maintaining complex state in Haskell

前端 未结 2 487
抹茶落季
抹茶落季 2020-12-22 18:47

Suppose you\'re building a fairly large simulation in Haskell. There are many different types of entities whose attributes update as the simulation progresses. Let\'s say, f

相关标签:
2条回答
  • 2020-12-22 19:27

    I know this is old topic. But I am facing the same problem right now while trying to implement Rail Fence cipher exercise from exercism.io. It is quite disappointing to see such a common problem having such poor attention in Haskell. I don't take it that to do some as simple as maintaining state I need to learn FRP. So, I continued googling and found solution looking more straightforward - State monad: https://en.wikibooks.org/wiki/Haskell/Understanding_monads/State

    0 讨论(0)
  • 2020-12-22 19:38

    The answer is functional reactive programming (FRP). It it a hybrid of two coding styles: component state management and time-dependent values. Since FRP is actually a whole family of design patterns, I want to be more specific: I recommend Netwire.

    The underlying idea is very simple: You write many small, self-contained components each with their own local state. This is practically equivalent to time-dependent values, because each time you query such a component you may get a different answer and cause a local state update. Then you combine those components to form your actual program.

    While this sounds complicated and inefficient it's actually just a very thin layer around regular functions. The design pattern implemented by Netwire is inspired by AFRP (Arrowized Functional Reactive Programming). It's probably different enough to deserve its own name (WFRP?). You may want to read the tutorial.

    In any case a small demo follows. Your building blocks are wires:

    myWire :: WireP A B
    

    Think of this as a component. It is a time-varying value of type B that depends on a time-varying value of type A, for example a particle in a simulator:

    particle :: WireP [Particle] Particle
    

    It depends on a list of particles (for example all currently existing particles) and is itself a particle. Let's use a predefined wire (with a simplified type):

    time :: WireP a Time
    

    This is a time-varying value of type Time (= Double). Well, it's time itself (starting at 0 counted from whenever the wire network was started). Since it doesn't depend on another time-varying value you can feed it whatever you want, hence the polymorphic input type. There are also constant wires (time-varying values that don't change over time):

    pure 15 :: Wire a Integer
    
    -- or even:
    15 :: Wire a Integer
    

    To connect two wires you simply use categorical composition:

    integral_ 3 . 15
    

    This gives you a clock at 15x real time speed (the integral of 15 over time) starting at 3 (the integration constant). Thanks to various class instances wires are very handy to combine. You can use your regular operators as well as applicative style or arrow style. Want a clock that starts at 10 and is twice the real time speed?

    10 + 2*time
    

    Want a particle that starts and (0, 0) with (0, 0) velocity and accelerates with (2, 1) per second per second?

    integral_ (0, 0) . integral_ (0, 0) . pure (2, 1)
    

    Want to display statistics while the user presses the spacebar?

    stats . keyDown Spacebar <|> "stats currently disabled"
    

    This is just a small fraction of what Netwire can do for you.

    0 讨论(0)
提交回复
热议问题