├── .gitignore ├── elm-package.json ├── README.md ├── LICENCE └── src └── Automaton.elm /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "summary": "experimental library for structuring code, based on Arrowized FRP", 4 | "repository": "https://github.com/evancz/automaton.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "Automaton" 11 | ], 12 | "dependencies": { 13 | "elm-lang/core": "5.0.0 <= v < 6.0.0" 14 | }, 15 | "elm-version": "0.18.0 <= v < 0.19.0" 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arrowized FRP for Elm 2 | 3 | This library is for structuring reactive code. The key concepts come 4 | directly from [Arrowized FRP][afrp]. It is not yet clear how 5 | valuable this is, so it is a great domain for experimentation and iteration 6 | to see if we can make it a really useful tool. 7 | 8 | This library aims to be a simple and minimal API that will help you get 9 | started with Arrowized FRP (AFRP), which can be very hard to understand 10 | from just the academic papers. From there, let us know on [the mailing 11 | list](https://groups.google.com/forum/#!forum/elm-discuss) if you wrote 12 | a larger program with it or have ideas of how to extend the API. 13 | 14 | [afrp]: http://haskell.cs.yale.edu/wp-content/uploads/2011/02/workshop-02.pdf 15 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015, Evan Czaplicki 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/Automaton.elm: -------------------------------------------------------------------------------- 1 | module Automaton 2 | exposing 3 | ( Automaton 4 | , pure 5 | , state 6 | , hiddenState 7 | , step 8 | , (>>>) 9 | , (<<<) 10 | , branch 11 | , pair 12 | , merge 13 | , first 14 | , second 15 | , combine 16 | , loop 17 | , count 18 | , average 19 | ) 20 | 21 | {-| This library is for structuring reactive code. The key concepts come 22 | directly from [Arrowized FRP][afrp]. It is not yet clear how 23 | valuable this is, so it is a great domain for experimentation and iteration 24 | to see if we can make it a really useful tool. 25 | 26 | This library aims to be a simple and minimal API that will help you get 27 | started with Arrowized FRP (AFRP), which can be very hard to understand 28 | from just the academic papers. From there, let us know on [the mailing 29 | list](https://groups.google.com/forum/#!forum/elm-discuss) if you wrote 30 | a larger program with it or have ideas of how to extend the API. 31 | 32 | [afrp]: http://haskell.cs.yale.edu/wp-content/uploads/2011/02/workshop-02.pdf 33 | 34 | # Automatons 35 | @docs Automaton 36 | 37 | # Create 38 | @docs pure, state, hiddenState 39 | 40 | # Evaluate 41 | @docs step 42 | 43 | # Combine 44 | @docs (>>>), (<<<), branch, pair, merge, first, second, combine, loop 45 | 46 | # Common Automatons 47 | @docs count, average 48 | -} 49 | 50 | 51 | {-| Think of an automaton as a little robot. It takes in `a` values and spits 52 | out `b` values. These robots can also remember stuff. If you put two of these 53 | robots next to each other you can do fancier stuff. 54 | -} 55 | type Automaton a b 56 | = Step (a -> ( Automaton a b, b )) 57 | 58 | 59 | {-| Step an automaton forward once with a given input. 60 | 61 | Say we start with the `count` automaton, which begins with the counter at zero. 62 | When we run `step 42 count` we get back a new automaton with the counter at 63 | 1 and the value 1. The original `count` automaton is unchanged, so we need to 64 | use the new automaton to use the latest state. 65 | -} 66 | step : i -> Automaton i o -> ( Automaton i o, o ) 67 | step a (Step f) = 68 | f a 69 | 70 | 71 | {-| Compose two automatons into a pipeline. For example, lets say we have a way 72 | to gather wood from the trees and a way to build a ship out of wood. 73 | 74 | gatherWood : Automaton Trees Wood 75 | buildShip : Automaton Wood Ship 76 | 77 | createShip : Automaton Trees Ship 78 | createShip = gatherWood >>> buildShip 79 | -} 80 | (>>>) : Automaton i inner -> Automaton inner o -> Automaton i o 81 | (>>>) f g = 82 | Step <| 83 | \a -> 84 | let 85 | ( f_, b ) = 86 | step a f 87 | 88 | ( g_, c ) = 89 | step b g 90 | in 91 | ( f_ >>> g_, c ) 92 | 93 | 94 | {-| Compose two automatons into a pipeline. For example, lets say we have a way 95 | to gather wood from the trees and a way to build a ship out of wood. 96 | 97 | gatherWood : Automaton Trees Wood 98 | buildShip : Automaton Wood Ship 99 | 100 | createShip : Automaton Trees Ship 101 | createShip = buildShip <<< gatherWood 102 | -} 103 | (<<<) : Automaton inner o -> Automaton i inner -> Automaton i o 104 | (<<<) g f = 105 | Step <| 106 | \a -> 107 | let 108 | ( f_, b ) = 109 | step a f 110 | 111 | ( g_, c ) = 112 | step b g 113 | in 114 | ( g_ <<< f_, c ) 115 | 116 | 117 | {-| Take a single input and branch it out into two different results. 118 | 119 | buildShip : Automaton Wood Ship 120 | buildHouse : Automaton Wood House 121 | 122 | build : Automaton Wood (Ship,House) 123 | build = branch buildShip buildHouse 124 | -} 125 | branch : Automaton i o1 -> Automaton i o2 -> Automaton i ( o1, o2 ) 126 | branch f g = 127 | Step <| 128 | \a -> 129 | let 130 | ( f_, b ) = 131 | step a f 132 | 133 | ( g_, c ) = 134 | step a g 135 | in 136 | ( branch f_ g_, ( b, c ) ) 137 | 138 | 139 | {-| Combine two independent automatons. The new automaton takes a pair of 140 | inputs and produces a pair of outputs. In this case we convert two separate 141 | values into two separate piles of wood: 142 | 143 | tsunami : Automaton Ship Wood 144 | tornado : Automaton House Wood 145 | 146 | disaster : Automaton (Ship,House) (Wood,Wood) 147 | disaster = pair tsunami tornado 148 | -} 149 | pair : Automaton i1 o1 -> Automaton i2 o2 -> Automaton ( i1, i2 ) ( o1, o2 ) 150 | pair f g = 151 | Step <| 152 | \( a, b ) -> 153 | let 154 | ( f_, c ) = 155 | step a f 156 | 157 | ( g_, d ) = 158 | step b g 159 | in 160 | ( pair f_ g_, ( c, d ) ) 161 | 162 | 163 | {-| Create an automaton that takes in a tuple and returns a tuple, but only 164 | transform the *first* thing in the tuple. 165 | 166 | build : Automaton Wood (Ship,House) 167 | upgradeShip : Automaton Ship Yacht 168 | 169 | buildNicer : Automaton Wood (Yacht,House) 170 | buildNicer = build >>> first upgradeShip 171 | 172 | It may be helpful to know about the following equivalence: 173 | 174 | first upgradeShip == pair upgradeShip (pure identity) 175 | -} 176 | first : Automaton i o -> Automaton ( i, extra ) ( o, extra ) 177 | first auto = 178 | Step <| 179 | \( i, ex ) -> 180 | let 181 | ( f, o ) = 182 | step i auto 183 | in 184 | ( first f, ( o, ex ) ) 185 | 186 | 187 | {-| Create an automaton that takes in a tuple and returns a tuple, but only 188 | transform the *second* thing in the tuple. 189 | 190 | build : Automaton Wood (Ship,House) 191 | upgradeHouse : Automaton House Palace 192 | 193 | buildNicer : Automaton Wood (Ship,Palace) 194 | buildNicer = build >>> second upgradeHouse 195 | 196 | It may be helpful to know about the following equivalence: 197 | 198 | second upgradeHouse == pair (pure identity) upgradeHouse 199 | -} 200 | second : Automaton i o -> Automaton ( extra, i ) ( extra, o ) 201 | second auto = 202 | Step <| 203 | \( ex, i ) -> 204 | let 205 | ( f, o ) = 206 | step i auto 207 | in 208 | ( second f, ( ex, o ) ) 209 | 210 | 211 | {-| Create an automaton that takes a branched input and merges it into a single 212 | output. 213 | 214 | disaster : Automaton (Ship,House) (Wood,Wood) 215 | pileWood : Wood -> Wood -> Wood 216 | 217 | disasterRelief : Automaton (Ship,House) Wood 218 | disasterRelief = disaster >>> merge pileWood 219 | 220 | It may be helpful to notice that merge is just a variation of `pure`: 221 | 222 | merge plieWood == pure (\(logs,sticks) -> pileWood logs sticks) 223 | -} 224 | merge : (i1 -> i2 -> o) -> Automaton ( i1, i2 ) o 225 | merge f = 226 | pure (uncurry f) 227 | 228 | 229 | {-| Turn an automaton into a loop, feeding some of its output back into itself! 230 | This is how you make a stateful automaton the hard way. 231 | 232 | type Feelings = Happy | Sad 233 | 234 | stepPerson : (Action, Feelings) -> (Reaction, Feelings) 235 | 236 | person : Automaton Action Reaction 237 | person = loop Happy (pure stepPerson) 238 | 239 | This example is equivalent to using `hiddenState` to create a `person`, but the 240 | benefit of loop is that you can add state to *any* automaton. We used 241 | `(pure stepPerson)` in our example, but something more complex such as 242 | `(branch f g >>> merge h)` would work just as well with `loop`. 243 | -} 244 | loop : state -> Automaton ( i, state ) ( o, state ) -> Automaton i o 245 | loop state auto = 246 | Step <| 247 | \input -> 248 | let 249 | ( auto_, ( output, state_ ) ) = 250 | step ( input, state ) auto 251 | in 252 | ( loop state_ auto_, output ) 253 | 254 | 255 | {-| Combine a list of automatons into a single automaton that produces a 256 | list. 257 | -} 258 | combine : List (Automaton i o) -> Automaton i (List o) 259 | combine autos = 260 | Step <| 261 | \a -> 262 | let 263 | ( autos_, bs ) = 264 | List.unzip (List.map (step a) autos) 265 | in 266 | ( combine autos_, bs ) 267 | 268 | 269 | {-| Create an automaton with no memory. It just applies the given function to 270 | every input. 271 | 272 | burnCoal : Coal -> Energy 273 | 274 | powerPlant : Automaton Coal Energy 275 | powerPlant = pure burnCoal 276 | 277 | The term *pure* refers to the fact that [the same input will always result in 278 | the same output](http://en.wikipedia.org/wiki/Pure_function). 279 | -} 280 | pure : (a -> b) -> Automaton a b 281 | pure f = 282 | Step (\x -> ( pure f, f x )) 283 | 284 | 285 | {-| Create an automaton with state. Requires an initial state and a step 286 | function to step the state forward. For example, an automaton that counted 287 | how many steps it has taken would look like this: 288 | 289 | count = Automaton a Int 290 | count = state 0 (\_ c -> c+1) 291 | 292 | It is a stateful automaton. The initial state is zero, and the step function 293 | increments the state on every step. 294 | -} 295 | state : b -> (a -> b -> b) -> Automaton a b 296 | state s f = 297 | Step <| 298 | \x -> 299 | let 300 | s_ = 301 | f x s 302 | in 303 | ( state s_ f, s_ ) 304 | 305 | 306 | {-| Create an automaton with hidden state. Requires an initial state and a 307 | step function to step the state forward and produce an output. 308 | 309 | type Feelings = Happy | Sad 310 | 311 | stepPerson : Action -> Feelings -> (Reaction, Feelings) 312 | 313 | person : Automaton Action Reaction 314 | person = hiddenState Happy stepPerson 315 | 316 | Notice that a `person` has feelings, but like [the 317 | Behaviorists](http://en.wikipedia.org/wiki/Behaviorism), we do not need to 318 | worry about that as an outside observer. 319 | -} 320 | hiddenState : s -> (i -> s -> ( o, s )) -> Automaton i o 321 | hiddenState s f = 322 | Step <| 323 | \x -> 324 | let 325 | ( s_, out ) = 326 | f x s 327 | in 328 | ( hiddenState out f, s_ ) 329 | 330 | 331 | {-| Count the number of steps taken. 332 | -} 333 | count : Automaton a Int 334 | count = 335 | state 0 (\_ c -> c + 1) 336 | 337 | 338 | type alias Queue t = 339 | ( List t, List t ) 340 | 341 | 342 | empty = 343 | ( [], [] ) 344 | 345 | 346 | enqueue x ( en, de ) = 347 | ( x :: en, de ) 348 | 349 | 350 | dequeue q = 351 | case q of 352 | ( [], [] ) -> 353 | Nothing 354 | 355 | ( en, [] ) -> 356 | dequeue ( [], List.reverse en ) 357 | 358 | ( en, hd :: tl ) -> 359 | Just ( hd, ( en, tl ) ) 360 | 361 | 362 | {-| Computes the running average of the last `n` inputs. 363 | -} 364 | average : Int -> Automaton Float Float 365 | average k = 366 | let 367 | step n ( ns, len, sum ) = 368 | if len == k then 369 | stepFull n ( ns, len, sum ) 370 | else 371 | ( (sum + n) / (toFloat len + 1), ( enqueue n ns, len + 1, sum + n ) ) 372 | 373 | stepFull n ( ns, len, sum ) = 374 | case dequeue ns of 375 | Nothing -> 376 | ( 0, ( ns, len, sum ) ) 377 | 378 | Just ( m, ns_ ) -> 379 | let 380 | sum_ = 381 | sum + n - m 382 | in 383 | ( sum_ / toFloat len 384 | , ( enqueue n ns_, len, sum_ ) 385 | ) 386 | in 387 | hiddenState ( empty, 0, 0 ) step 388 | 389 | 390 | 391 | {--TODO(evancz): See the following papers for ideas on how to make this 392 | library faster and better: 393 | 394 | - Functional Reactive Programming, Continued 395 | - Causal commutative arrows and their optimization 396 | 397 | Speeding things up is a really low priority. Language features and 398 | libraries with nice APIs and are way more important! 399 | --} 400 | --------------------------------------------------------------------------------