The “I’m stupid” elm language nugget #10

Note: updated for 0.18 — It’s really weird to read code from just a few months ago and see how much my elm style has changed. Elm 0.18 makes this even a little easier in that you can use Platform.program(withFlags).

I was messing with the code from last week and I was like … “What does it really take nowadays to do general computation in elm?” It turns out that although something like elm-console gives you a lot of control, if you just want to interact with an elm program in a terminal, it’s relatively easy:

As a reminder, the original stack code was written by stackoverflow user http://stackoverflow.com/users/307454/lifebalance , and I realized the mistake that kept me from generalizing the Stack type and wrote about it here https://medium.com/@prozacchiwawa/the-im-stupid-elm-language-nugget-9-2ec09e263fd8?source=reading_list---------0-2 .

A stack is always a nice jumping off point for an RPN calculator, so I decided to make one based on it:

port module Calc exposing (..)
import Json.Decode
import Lazy.List as LL exposing (LazyList)
import Platform exposing (program)
import String
import Stack exposing (..)
type Action
= NoOp
| Add
| Sub
| Mul
| Div
| Val Float
| Close
type alias Model = Stack Float
update action model =
let doBin action =
case action of
Add -> (+)
Sub -> (-)
Mul -> (*)
Div -> (/)
_ -> (\a b -> a)
in
let updatedModel =
let doBinary =
let (aa, m1) = pop model in
let (bb, m2) = pop m1 in
case (aa,bb) of
(Just a, Just b) ->
push (doBin action a b) m2
_ -> model
in
case action of
NoOp -> model
Add -> doBinary
Sub -> doBinary
Mul -> doBinary
Div -> doBinary
Val v -> push v model
Close -> model
in
(updatedModel,
case (action, pop updatedModel) of
(Close, (Just a, st)) -> Cmd.batch [output (toString a), end 0]
(Close, (Nothing, st)) -> end 1
(_, (Nothing, st)) -> output "Nothing"
(_, (Just a, st)) ->
updatedModel
|> stackIterator
|> LL.map toString
|> LL.toList |> String.join " " |> output)
  )
translateInputString str =
case str of
"+" -> Add
"-" -> Sub
"*" -> Mul
"/" -> Div
_ -> case String.toFloat str of
Ok v -> Val v
Err _ -> NoOp
main =
program
{ init = ([], Cmd.none)
, update = update
, subscriptions = \_ -> Sub.batch [input translateInputString, close (\_ -> Close)]
}
port input : (String -> msg) -> Sub msg
port output : String -> Cmd msg
port close : ({} -> msg) -> Sub msg
port end : Int -> Cmd msg

And a javascript driver:

var readline = require(‘readline’);
var elm = require(‘./calc’);
var runner = elm.Calc.worker();
var rl = readline.createInterface({input: process.stdin, output: process.stdout});
runner.ports.output.subscribe(function(s) { rl.output.write(s+’\n’); });
runner.ports.end.subscribe(function(n) { process.exit(n); });
rl.on(‘line’, function(cmd) { runner.ports.input.send(cmd); });
rl.on(‘close’, function() { runner.ports.close.send([]); });

Yay! It’s way not complicated anymore!

sh-3.2$ elm make — output calc.js Calc.elm
Success! Compiled 1 module.
Successfully generated calc.js
sh-3.2$ node runelm.js
3
3
7.1
7.1 3
10.2
10.2 7.1 3
19.7
19.7 10.2 7.1 3
+
29.9 7.1 3
/
4.211267605633803 3
*
12.633802816901408
^D
12.633802816901408
sh-3.2$

Importantly, it’s no longer necessary to do anything fiddly. You can just include a small driver and use your elm code in this way very easily.

Like what you read? Give art yerkes a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.