Showing posts with label Elm. Show all posts
Showing posts with label Elm. Show all posts

Thursday, March 6, 2014

Flow-Based Games

So I just tried to write what I thought was a fairly simple game prototype tentatively titled "Gratuitous Resource Gathering" using Elm. Something in the style of Cookie Clicker(DO NOT click that link if you were planning on doing anything today). Here's how far I got:

import Mouse
import Time

port resourceA : Signal Bool
port building : Signal String

buildingCost = foldp (+) 0 <| keepWhen (lift2 (>) (constant 50) balance) 0 <| sampleOn building <| constant 50

tickIncrement = foldp (+) 0 <| sampleOn buildingCost <| constant 0.01
tick = sampleOn (every Time.millisecond) <| constant True

spent = foldp (+) 0 <| merges [ sampleOn building buildingCost ]

gathered = foldp (+) 0 <| merges [ sampleOn resourceA <| constant 1
                                 , sampleOn tick tickIncrement ]

balance = lift round <| lift2 (-) gathered spent

main = lift (flow down) <| combine [ lift asText balance ]

That's almost the complete game, except for one very annoying detail: it doesn't work. When I try to run it in the appropriate HTML harness, I get

s2 is undefined
    Open the developer console for more details.

The reason is that buildingCost signal. When the user purchases a building, I need to check whether they have enough resource balance to buy it. However, the balance is the sum of two other signals, gathered and spent, the second of which is affected by building purchases. Looks like circular signals aren't a thing in Elm right now. I'm not sure how to resolve this inside the language, and I already told you all about ports last time, so my natural first reaction was to think about how I'd go about computing that price check outside the Elm module. Unfortunately, once I started mentally pulling things out of Elm, I quickly arrived at the conclusion that the whole thing would probably need to be turned inside-out. In other words, I'd be using Elm purely as a way of avoiding manual DOM manipulation in one or two components of a mostly Javascript project.

Maybe that'd still be worth it, but it feels quite unsatisfying.

No real idea what to do about it though. I'll talk to some people I consider smarter than me and see what they think. Hopefully there's a reasonable way around the problem that doesn't include doing most of it in manual JS.

In the meantime, I hacked together something in Daimio.

outer
        @resource-click dom-on-click resource
        @building-click dom-on-click building
        @show dom-set-html display

        @timer every-half-second

        $building-cost 5
        $click-increment 1
        $tick-increment 0
        $balance 0

        inc-balance 
                { __ | add $balance | >$balance }
        dec-balance 
                { __ | subtract value __ from $balance | >$balance }

        can-afford
                { __ | ($building-cost $balance) | max | eq $balance }
        
        @resource-click -> {__ | $click-increment } -> inc-balance -> @show
        @building-click -> can-afford -> { __ | then $building-cost else 0} -> dec-balance -> @show
                           can-afford -> { __ | then 10 else 0 | add $tick-increment | >$tick-increment }
                           can-afford -> { __ | then "Buying..." | tap }

        @timer -> {__ | $tick-increment } -> inc-balance -> { __ | $balance } ->  @show

No highlighting mode for that one yet; I'm workin' on it. Also, the above was kind of non-trivial because I had to add my own timer event, and I still want to figure out how to factor out the process of buying a building. But it works well enough on my machine. I'll post the full project up to my github once I do a bit more thinking about it.

Monday, February 17, 2014

Autocompletion Example with Ports in Elm

Two things on the agenda today. First, Elm has gotten some improvements that might mean I end up using it in production at some point. Second, I tried a new language called Pure, which I found by searching for "dynamically typed haskell". Stick around if that sounds interesting.

EDIT:

Do not bother sticking around if that sounds interesting. I ended up talking about Elm so much that I never got into Pure.

Tue, 18 Feb, 2014

Elm Lang

For those of you just joining us, Elm is a pure-functional, statically typed, optionally-type-inferring language closely based on Haskell, which targets a JavaScript-hosted VM for deployment. That is, there's an elm-runtime.js which Elm code compiles to target, and the result is highly reactive web front-ends that don't require any mucking around with the DOM. Now that we're all on the same page...

Elm. Again.

This came up at a recent Code Retreat, and it looks interesting as fuck in context with the FBP stuff I've been doing at work recently. The problem we were solving at the event was autocompletion. That is, given a partial input, return possible completions from some dictionary. Here's a short[1] implementation in Elm.

module Autocomplete where

import String
import Keyboard
import Graphics.Input as Input

(field, content) = Input.field "Enter text"

fState : [String] -> Input.FieldState
fState comps = case comps of
                 [] -> {string = "", selectionStart=0, selectionEnd=0}
                 _  -> {string = (head comps), selectionStart=0, selectionEnd=0}

esc = Keyboard.isDown 27
ctrlSpace = dropRepeats . lift and <| combine [Keyboard.ctrl, Keyboard.space]

empty : Signal Element
empty = sampleOn (merge Keyboard.enter esc) . fst <| Input.field "Enter text"

completeElem : Signal Element
completeElem = lift ((Input.fields Input.emptyFieldState).field id "Enter text") . sampleOn ctrlSpace . lift fState <| lift completions content

completions : String -> [String]
completions partial = if | 0 < String.length partial -> filter (String.startsWith partial) wordList
                         | otherwise          -> []

main = lift2 above (merges [field, completeElem, empty]) . lift asText <| lift completions content

--- Dummy data
wordList : [String]
wordList = String.words "one two three four five six seven eight nine ten"

The point here is: there's an input that displays completions as you type. If you hit the enter or esc keys, that input is cleared, and if you hit Ctrl+Space, it's filled with the top completion. The above doesn't let you select a different completion, which it should, but it's a pretty instructive example.

Lets go through it.

module Autocomplete where

import String
import Keyboard
import Graphics.Input as Input

Module and import declarations. Nothing to see here, move along.

(field, content) = Input.field "Enter text"

fState : [String] -> Input.FieldState
fState comps = case comps of
                 [] -> {string = "", selectionStart=0, selectionEnd=0}
                 _  -> {string = (head comps), selectionStart=0, selectionEnd=(String.length <| head comps)}

The first line in this bit sets up a field, which is represented as a pair of Signals; one for the element and one for the content. Signals are a pretty good way of modeling state changes over time in a purely-functional context. You can think of one as the infinite stream of possible values it'll contain, the current of which your program will be continuously operating. An input field is a pair of signals because you'd like to be able to change it[2], as well as receive updates about its state changes. We'll do that by combining several signals on particular sample points, and FieldState is the type we can eventually funnel into a field.

esc = Keyboard.isDown 27
ctrlSpace = dropRepeats . lift and <| combine [Keyboard.ctrl, Keyboard.space]

These represent two different signals we'd like from the Keyboard module. The first will be True whenever the Escape key is down[3], the second will be True when both the Ctrl and Space key are pressed[4]. The types at each step might be useful. In particular, ctrlSpace : Signal Bool, combine : [Signal a] -> Signal [a] and lift and : Signal [Bool] -> Signal Bool. The dropRepeats is the only chunklet whose type signature will give you no further understanding [5]; it's there to prevent partial signal changes from triggering a "change" in the ctrlSpace signal itself. Also, on a syntax note, the <| is identical to Haskell's $.

Onward.

empty : Signal Element
empty = sampleOn (merge Keyboard.enter esc) . fst <| Input.field "Enter text"

completeElem : Signal Element
completeElem = lift ((Input.fields Input.emptyFieldState).field id "Enter text") . sampleOn ctrlSpace . lift fState <| lift completions content

completions : String -> [String]
completions partial = if | 0 < String.length partial -> filter (String.startsWith partial) wordList
                         | otherwise          -> []

This is the real meat right here. empty is the signal of empty elements which will "changes" whenever the enter or esc keys are pressed[6]. completeElem is the signal of filled elements that "changes" whenever the user hits Ctrl + Space. Finally, completions is the signal of completions of the current text in the main input.

main = lift2 above (merges [field, completeElem, empty]) . lift asText <| lift completions content

--- Dummy data
wordList : [String]
wordList = String.words "one two three four five six seven eight nine ten"

These remaining lines render the input and completions to screen, and set up the extremely minimal test dictionary. That's it. What we have here is exactly what was described. An input, backed by a word list, which is cleared on either Enter or Esc, and completed on Ctrl+Space.

The New Part

None of that was new.

If you've read any of the articles on this blog tagged Elm, you'd have known all of it already. The new part is that you can now have your Elm programs communicate with the outside world. In the case we're considering above, a solitary auto-completing input is pretty useless. But imagine if you could use it essentially as a minibuffer in a larger project. You'd want to be able to pass it new completion lists, and you'd want it to notify you when the user entered some input. It goes without saying that you'd like all this to be encapsulated within a known, non-global namespace, so that you could combine your Elm minibuffer with arbitrary JS projects.

So, lets do it.

module Autocomplete where

import String
import Keyboard
import Graphics.Input as Input

(field, content) = Input.field "Enter text"

fState : [String] -> Input.FieldState
fState comps = case comps of
                 [] -> {string = "", selectionStart=0, selectionEnd=0}
                 _  -> {string = (head comps), selectionStart=0, selectionEnd= (String.length <| head comps)}

esc = Keyboard.isDown 27
ctrlSpace = dropRepeats . lift and <| combine [Keyboard.ctrl, Keyboard.space]

empty : Signal Element
empty = sampleOn (merge Keyboard.enter esc) . fst <| Input.field "Enter text"

completeElem : Signal Element
completeElem = lift ((Input.fields Input.emptyFieldState).field id "Enter text") . sampleOn ctrlSpace . lift fState <| lift2 completions content wordList

completions : String -> [String] -> [String]
completions partial wordList = if | 0 < String.length partial -> filter (String.startsWith partial) wordList
                                  | otherwise          -> []

main = lift2 above (merges [field, completeElem, empty]) . lift asText <| lift2 completions content wordList

port wordList : Signal [String]

port output : Signal String
port output = keepIf (\s -> s/="") "" (sampleOn Keyboard.enter content)

That's a minimally changed .elm file. The differences are

  • We've added port declarations at the bottom there. One incoming, which is just a type declaration, and one outgoing, which has a type declaration and a transmitter function.
  • We've changed completions so that it takes its wordList as input
  • Anywhere we used to call completions with lift completions content, we now have to call it with lift2 completions content wordList

The file you'd embed that module into would look something like this[7]

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Embedding Autocomplete - Elm</title>
    <script type="text/javascript" src="/elm-runtime.js"></script>
    <script type="text/javascript" src="/build/Autocomplete.js"></script>
  </head>
  <body>
    <div id="auto" style="position: absolute; left: 50px; top: 50px; width: 600px; height: 100px; border: 2px dashed #000;"></div>
    <script type="text/javascript">
      var can = Elm.embed(Elm.Autocomplete, 
                          document.getElementById("auto"), 
                          {wordList: ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]});
      can.ports.output.subscribe(function (msg) { console.log("FROM MINIBUFFER :: ", msg) })
    </script>
  </body>
</html>
EDIT:

You can find a running demo of the above here.

Sat, 22 Feb, 2014

The relevant bits are the positioned div, which will contain our program, and the Elm.embed call, which sets it up. Note especially the third argument; you have to do that for any input ports in the component you're embedding. Finally, note the subscribe call which fits that output port we defined with a listener, in this case a naive one that just prints everything it gets to the console.

This is awesome.

It's awesome enough that I'm seriously considering Elm for some production work at work. Because I want to apply Elm in the places where it'll do massive amounts of good, and leave the other stuff to stateful JavaScript programs. Using the ports approach above, I can get exactly that. If there was something similar for Hskell, I'd probably have taken the plunge and built something with it by now[8].

In Case You're Reading, evancz

There are still a few minor headaches with the language, though thankfully I didn't have to stub my toe on most of them this time around. The only ones that ended up being annoying, or will be very shortly are

  • No signal defaults from within .elm files. This bites during development. When you have an Elm module that will depend on an outside signal for its operation, you have to set a default value for that signal outside. This is ok once you've got the embedding file together, but it does mean that that second Autocomplete.elm file above will give you the error
    Initialization Error: port 'wordList' was not given an input!
        Open the developer console for more details.
    if you try to run it standalone without modifications. The workaround I've been using is to comment out the port declaration line, and add one that reads wordList = constant ["one", "two", "three", "four", "five", "six"]. It works, but I'd rather not have to do it.
  • No Haskell-style sections. It only bit once in this program, and it's tolerable, but I'd much rather write (/="") than the equivalent, but syntactically noisier (\s -> s /= "").
  • No Indexing. I'm almost convinced this has to be an omission on my part, and there's actually a way to do it out of the box, because it seems mildly bizarre to have List.head and String.sub in a language, but no list indexing operator or function. If there is one, just point me to it. In the meantime, you can define your own minimal version as (!!) lst ix = lst |> drop ix |> head, or maybe
    (!!) lst ix = case drop ix lst of
                    [] -> Nothing
                    sub -> Just <| head sub
    if getting out of array bounds gives you pause. Neither of these deals with negative indices, but they'll give you trivial indexing capabilities.

That's that, I guess. I was going to talk a bit about Pure. And Forth. And maybe incidentally a bit about C, but this is way longer than I was expecting already, so I think I'll call it for today.


Footnotes

1 - [back] - Admittedly this took something like hours total. Writing it involved a lot of experimentation and documentation browsing, and I did a quick clean-up pass afterwards. I'm really hoping the development time goes down as I get more practice with the language and type system.

2 - [back] - Actually hang on. If you're new to the FRP thing, I should clarify that you never really change things. Remember; any stateful component is represented as the lazy, infinite list of its complete history. What I really meant by the shorthand "change it" is "merge multiple signals of the same type in a way that gives one of them primacy in certain situations". It's counter-intuitive the first few times, but it's helpful to keep the perspective in mind when you're dealing with Elms.

3 - [back] - And hence will change on a keyDown or keyUp event for that key.

4 - [back] - And will therefore change on either ctrl/space keyDown, whichever is second and on ctrl/space keyUp, whichever is first.

5 - [back] - It's dropRepeats : Signal a -> Signal a, in case you really, really care.

6 - [back] - That's the (merge Keyboard.enter esc).

7 - [back] - Assuming those were accurate urls for elm-runtime.js and Autocomplete.js on your system.

8 - [back] - For the record, there might be something like it for Haskell, you'll hear excited giggling from me if I happen to find it. For the moment, I'm still evaluating the FRP section of the Hasekellwiki.

Saturday, June 22, 2013

Elm In Practice

So I've gotten some time in with it. Not quite enough to finalize the new interface, though I do have an unstyled 95% version running on my local with an apropos choice of music. Firstly, here's the code.

The Code

module Mote where

import JavaScript.Experimental (toRecord)
import Json (fromString, toJSObject)
import Graphics.Input (button, buttons, customButtons)
import Window (middle)
import Http (sendGet, send, post)
import Maybe (maybe)

----- Signal Declarations
uriDir str = "/show-directory?dir=" ++ str
reqPlay str = post ("/play?target=" ++ (maybe "" id str)) ""
reqCmd str = post ("/command?command=" ++ (maybe "" id str)) ""

command = buttons Nothing
playing = buttons Nothing
files = buttons "root"

dir = sendGet $ lift uriDir files.events
cmd = send $ lift reqCmd command.events
ply = send $ lift reqPlay playing.events

----- Utility
jstrToRec jStr = let conv = toRecord . toJSObject
                 in maybe [] conv $ fromString jStr

----- Application
box n = container 350 n midTop

cmdButton name = height 42 $ width 80 $ command.button (Just name) name

controls = flow down [ box 48 $ flow right $ map cmdButton ["backward", "stop", "pause", "forward"]
                     , box 50 $ flow right $ map cmdButton ["volume-down", "volume-off", "volume-up"]]
           
entry { name, path, entryType } = let btn = if | entryType == "return" -> files.button path
                                               | entryType == "directory" -> files.button path
                                               | otherwise -> playing.button (Just path)
                                           in width 350 $ btn name

showEntries res = case res of
  Success str -> flow down . map entry $ jstrToRec str
  _ -> plainText "Waiting..."

showMe entries = flow down [ box 100 $ controls
                           , showEntries entries ] 

main = showMe <~ dir

And that's all. Seriously. This replaces all of the ~200 lines of JS/HTML/CSS that comprised the Angular.js edition, and the ~300 lines of its jQuery/Backbone predecessor.

So, if nothing else, Elm is very terse.

module Mote where

import JavaScript.Experimental (toRecord)
import Json (fromString, toJSObject)
import Graphics.Input (button, buttons, customButtons)
import Window (middle)
import Http (sendGet, send, post)
import Maybe (maybe)

That first part is the module declaration and imports, hopefully self-explanatory.

----- Signal Declarations
uriDir str = "/show-directory?dir=" ++ str
reqPlay str = post ("/play?target=" ++ (maybe "" id str)) ""
reqCmd str = post ("/command?command=" ++ (maybe "" id str)) ""

command = buttons Nothing
playing = buttons Nothing
files = buttons "root"

dir = sendGet $ lift uriDir files.events
cmd = send $ lift reqCmd command.events
ply = send $ lift reqPlay playing.events

This declares the main signals of the interaction, and some uri/request helper functions they'll need. command is the group of buttons that issues playback commands, playing is the group of buttons sending play instructions specifically, and files is the group of buttons sending show-directory commands. These were all handled by the same callback mechanism in earlier versions of the interface, but it makes sense to separate them if we're dealing with their signal streams. dir, cmd and ply just take event signals from those button groups, make appropriate Ajax requests when necessary, and return signals of responses.

----- Utility
jstrToRec jStr = let conv = toRecord . toJSObject
                 in maybe [] conv $ fromString jStr

That is a short utility function that converts a JSON string to a (potentially empty) list of records. The empty list situation happens in two cases

  • if the server sends back an empty list
  • if the server sends back a malformed JSON string
----- Application
box n = container 350 n midTop

cmdButton name = height 42 $ width 80 $ command.button (Just name) name

controls = flow down [ box 48 $ flow right $ map cmdButton ["backward", "stop", "pause", "forward"]
                     , box 50 $ flow right $ map cmdButton ["volume-down", "volume-off", "volume-up"]]
           
entry { name, path, entryType } = let btn = if | entryType == "return" -> files.button path
                                               | entryType == "directory" -> files.button path
                                               | otherwise -> playing.button (Just path)
                                           in width 350 $ btn name

showEntries res = case res of
  Success str -> flow down . map entry $ jstrToRec str
  _ -> plainText "Waiting..."

showMe entries = flow down [ box 100 $ controls
                           , showEntries entries ] 

main = showMe <~ dir

This is the meat of the front-end. box is just a positioning helper function. cmdButton is a helper function to define a playback command element. Note that these are missing a piece of functionality from the old interface: clicking and holding the rewind/forward/volume-up/volume-down buttons doesn't do anything. It used to make serial requests to the server for the appropriate command, but Elm doesn't have very good support for HTML events. I'll talk more about that in a bit.

controls defines the two-row, centered placement of those command elements. entry defines a button for the main show/play buttons which comprise the principal interaction with Web Mote. These are missing the play/shuffle sub-buttons for directories and they subtle styling, but that's just because I didn't do it yet. There's no obviously missing feature that would prevent me from implementing all of it; I'd just need to define the appropriate customButton and slot it in. I'd call it five lines at the outside. Thing is, I want to get to writing this article first, so it'll probably happen in an addendum.

Now that we've got that out of the way, here's what I think.

What I Think

To summarize, very good, but obviously not finished yet. Which makes sense, since it's only at 0.8. I'm going to go through the headaches first, then note the things I particularly like about working with it.

Headaches

Signal Hell

Or, alternately, "Type Hell". I'm putting this one front-and-center, because Elm's author is fiercely anti-callback, but seems to be just fine with introducing a similar situation with the type system.

The argument against callbacks goes like this in a nutshell: if you write one, you're separating pieces of a procedure that should really be unified. You want to express "do this stuff", but part of it has to happen after an asynchronous request, so you have to break your procedure up into pre-async and post-async stuff, then have the request call the function that completes post-async stuff after the request returns. It gets even worse if you need to do multiple async requests as part of your tasks; you might need to split the work up arbitrarily among a large number of functions, all of which should actually be unified conceptually.

Now, I'm not disagreeing with this argument, but take a look at the bottom of that code from Mote.elm.

showMe entries = flow down [ box 100 $ controls
                           , showEntries entries ] 

main = showMe <~ dir

What I want to express here is "Stack the controls on top of the file entries (figuring out entries based on the signal dir)". But you can't display an Element in the same list as a Signal Element because that would make some type theorist somewhere cry apparently. So instead of doing something like

main = flow down [ box 100 $ controls, showEntries $ id <~ dir]

I have to write a separate callback-like function to accept the sanitized signal value and display that instead.

This is the same situation as callback hell. The only difference is that callbacks separate your code at boundaries determined by asynchronous calls, while these signal display functions do it at boundaries determined by the type system. I guess one of those might be better than the other if you squint hard enough, but I'm not seeing it from here.

Very Few Event Options

A button or customButton send signals when they're clicked. input of type="text", passwords, checkboxes, and dropDowns send signals when their value changes. textarea and radio buttons don't exist. And that's all.

What do you do if you want a given form to submit when you hit Ret in a relevant input? What do you do if you want to define a button that can be held down (requiring a mouse-down event)? How do you implement draggables, or droppables, or datepickers, or any of the interactive pieces that jQuery has trivially provided since something like 2006? You either do it with global signals, or you make liberal use of the JavaScript FFI. Which isn't exactly fun. Since Elm is trying to do all of styling/content/behavior specification, I understand that you need to have elements like image that don't actually have behaviors. That is, they're of type Element rather than of type (Element, Signal a). But the ones that do send signals should have a menu of signals to provide. I mean, you already have this cool record syntax, what you could do is provide an interface for the user where,

button : String -> SignalOptions -> (Element, Signal a)

and SignalOptions is something like { click : a, mouseEnter: a, mouseLeave: a, mouseDown: a, mouseUp: a, keyDown: a }. Granted, maybe that shouldn't be a button, but rather a different multi-signal element, but it would give quite a bit more flexibility to front-end developers. If you had an element like that, you could easily implement any of the interactions I mention above.

No Encoding/Decoding Out-of-the-box

I'll probably implement something here when I get around to poking at the language again, but there's no built-in way to call encodeURI or encodeURIComponent from Elm. Which means that as written, this front-end will fail to play files with & in their name. That's less than ideal. I get the feeling it wouldn't be too hard to implement using the JS FFI, but I'm not diving into that right now.

Gimped Case

The Elm case statement doesn't pattern-match on strings. There's no mention of that behavior in the docs, so I'm not sure whether this is a bug or an unimplemented feature or what, but I missed it once in a ~50 line program. Specifically, in entries

entry { name, path, entryType } = let btn = if | entryType == "return" -> files.button path
                                               | entryType == "directory" -> files.button path
                                               | otherwise -> playing.button (Just path)
                                           in width 350 $ btn name

where I had to resort to using the new, otherwise unnecessary multi-branch if. Unfortunately ...

Gimped if Indentation

Because there's no elm-mode yet, you're stuck using haskell-mode for editing .elms. haskell-mode craps out on indentation of that multi-branch if statement I just mentioned. If you try to indent the following line, it'll yell at you about parse errors rather than inserting the appropriate amount of white-space, which makes working with an already unnecessary-feeling operator just that little bit more annoying. This is similar to that [markdown| |] tag indentation issue I mentioned last time, it's just that the Web Mote front-end port didn't happen to need any markdown.

Gratuitous Differences

Type annotation (::) and cons (:) from Haskell have been switched for no obvious reason, and if seems to have a similar treatment. Unlike most of the other things I bumped into, this and the case "bug" have no hope in hell of being solved by a mere user of the language, so hopefully the designer does something about them.

Nitpicks

These aren't big things, and they're not really related to the language itself, but I noticed them and they were annoying.

No Single-File Option

This is just a nice to have. It would have made this front-end marginally easier to deploy, but I'm not sure how it would work if you needed more than one file served for your program. Elm targets JavaScript as a platform, which means that the base language is deployed as a js file that you have to host manually if you're not using the elm-server. When you compile an Elm project, you have an option that looks like this

  -r --runtime=FILE           Specify a custom location for Elm's runtime
                              system.

It's slightly misleading, because what it actually does is specify where to load elm-runtime.js from in the compiled file. Literally, it determines the src property of the appropriate script tag. For that Mote front-end, I had to elm --make -r "/static/js/elm-runtime.js" --minify Mote.elm, and then make sure to serve elm-runtime.js from that static url (by default, you can find this file in ~/.cabal/share/Elm-0.8.0.3/elm-runtime.js, in case you were wondering).

Anyhow, it would be nice if there was a compiler option you could activate to just have this runtime inlined in your compiled result, rather than served separately.

Unstable Website

elm-lang.org is down pretty frequently. It seems to be up at the moment, but I'm not sure how long that's going to be the case. It happens often enough that I just went ahead and did a checkout from its github. Then I found out that the "Documentation" pages happen to be missing from that repo...

Highlights

Anything I didn't mention above is good, which is to say "most of it", but there are two things I like about the language enough to call out.

Records

This is brilliant. Take a bow, you've nailed record interaction. The approach probably wouldn't fit trivially into GHC, but it would solve some of the problems their records have. It's also something the Erlang devs should probably keep an eye on, because it's much much better than what I remember having access to in Erl-land. Probably the biggest win is that Elm records get first-class treatment in terms of the languages' pattern matching facilities, which lets you do things like

entry { name, path, entryType } = let btn = if | entryType == "return" -> files.button path
...

That's something I miss in almost every single language that has both pattern matching and k/v constructs. As usual, Common Lisp has a 95% solution as part of the Optima pattern matching library.

This dynamic record syntax also lets you trivially handle JSON input from a server. In case you didn't notice, the stuff I was passing into entry originates in ajax responses from the server.

Haskell-grade Terseness

Just a reminder. Despite all those flaws I pointed out above, the Elm version of this particular program weighs in at about 1/4 the code of the reactive Angular.js version, let alone the traditional plain DOM/jQuery approach. It's also more pleasant to work with than JS, but that's an entirely subjective point. Improvements can still be made here; implementing haskell-style sections and multi-line definitions would save a bit of typing, though, to be fair, not as much as I thought it would.

Conclusions

I've already mentioned that I'm going to take a swing at putting together some SSE support, encodeURI(component)? calls and a more appropriate Emacs mode for Elm, but it probably won't be very soon. Thanks to a tip-off from Dann, I managed to squeak into the registration for the Lisp In Summer Projects event, which looks very much like a multi-month NaNoWriMo with parentheses instead of character development and sleep.

I'm going to make a serious attempt at getting a little pet project of mine up-and-running in either Common Lisp or Clojure by September 30, which means I'll have very little time to hack on someone else's up-and-coming language regardless of how interesting it looks.

Tuesday, June 18, 2013

Dragging in an FRP Context

I made an off-the-cuff remark earlier to the effect that Elm doesn't let you easily define drag/drop functionality, or element-originating clicks. Really, the situation is that you can't easily work with any of the basic HTML events, which also include hovering, element-originating keypresses, various window events, and various form events. When you think about how you'd implement any of them individually, it starts to become obvious why that is.

The first reflex is to reach for callbacks. Which, as was already discussed, is the exact opposite of what Elm is trying to do. The real trouble begins when you consider how you'd do the same thing without callbacks in order to preserve that purity of purpose.

First Pass

The obvious solution is to use a bunch of signals everywhere. One for each of the element-based events. Let the user specify signal values on elements, and dispatch on their results at the other end.

Except thats quite complex.

At first glance, you're looking at twenty or so global signals, each of which are going to have the kind of isolated, complicated dispatch we saw in that Tic Tac Toe example. That sounds worse in every way than callback hell; all your dispatch needs to be centralized, which means that behavior under various circumstances will by definition be separated from the element it pertains to, and you suddenly can't understand any component of your program without understanding the central signal dispatch code.

Second Pass

Another approach might be not to let the user specify signal values. Make them hooks to the relevant element. Expose some kind of interface to the user so that they can pipe other signal values into various properties of that element, and call it a day.

Also, we don't really need to have a signal per HTML event. For the situations I'm currently thinking about, we could get away with exactly two. Keyboard.focus and Mouse.focus will give me most of what I'd want in a pretty simple way. Basically, have mouseover, mouseout, mousedown, mouseup and mouseclick send this over the Mouse.focus signal, and let mouseclick, esc and tab send the same over the Keyboard.focus signal.

You'd then have some idea of what needs to be moved as a result.

User Side

Of course, that's all base implementation stuff. On the client side, you don't want to have to do things like maintain your own table of draggables to dispatch a signal to when relevant. You'd want to be able to do something like

draggable dragDefs $ plainText "This text is draggable"

and have that tap the right signals so that when you mousedown or touch on "This text is draggable", it starts moving along with the cursor. In basic terms what needs to happen is

  • when the mouse down signal is being sent
  • and the Mouse.focus signal is referring to a draggable
  • start piping cursor position, modified by initial deltas, into the x and y coordinates of that element

and I have no idea what the appropriate way to express that is in the framework of the existing Elm language.

It sounds like it might just be easier to avoid those interactions while I'm starting out. SSEs sound like they'd be a much easier first feature, actually.

SSEs

The reason being that, when you think about it, this fits perfectly into the FRP paradigm. A source is a signal whose value is the latest matching message body and/or id. That's it. You'd want the declaration to look something like

src = eventSource "/my/source/uri" ["message type 1", "message type 2" ...]

at which point src should be a signal you can pass around, whose current value will be the latest message coming out of "/my/source/uri" that has one of the message types specified. It might also be useful to handle unlabeled messages, at which point our message needs to look something like

data SSE = SSE { id : Maybe Int, label : Maybe String, body : String }

Manageable, if slightly annoying due to the optional fields.

You'd implement a rolling message by piping src through plainText . .body, and you could put together a very simple chat program with some judicious use of foldp.

These were all just some random thoughts I wanted a good look at, for the time being. Like I said, I'll be throwing my next few spare hours at putting together an Elm-based WebMote front-end. Fortunately, this task doesn't involve any in-depth interaction, and the SSEs aren't central to the exercise.

Monday, June 17, 2013

Elm First Impressions

For the past little while, I've been poking around a new language named Elm. A Haskell-like web front-end language with a heavy focus on FRP. Actually, no, it's not like Haskell, its syntax is Haskell except for a few omissions[1], a couple justifiable small changes, and a couple pointlessly gratuitous differences[2]. To the point that the actual, official recommendation is to just use Haskell mode to edit Elm files.

This works pretty well, except for one thing: Elm has a built-in reader macro for Markdown input. Using this feature in Haskell mode plays all kinds of hell with your indentation and highlighting. Enough that I thought it worth-it to hack a workaround in using two-mode-mode. This is far from ideal, but bear with me. You need to get two-mode-mode from that previous link, do a search/replace for mode-name into major-mode, and delete the line that reads (make-local-hook 'post-command-hook). Then, you have to add the following to your .emacs somewhere:

(require 'two-mode-mode)
(setq default-mode (list "Haskell" 'haskell-mode)
      second-modes (list (list "Markdown" "\[markdown|" "|\]" 'markdown-mode)))

and then run two-mode-mode whenever you're editing .elm files. The end result is that, whenever you enter a markdown block with your cursor, your major mode will automatically change to markdown-mode, and change back to haskell-mode when you leave. There has to be a better solution than this, probably involving one of the other Multiple Modes modules, and I'll put some thought into it when I get a bit of time.

Installation/Basics

Installing is ridiculously easy. If you've ever installed a module for Haskell, you won't have trouble. It's just cabal update; cabal install elm elm-server. Do the update first, like it says there; the language hasn't reached 1.0 status as of this writing, which means that it's quite likely there will be significant changes by the time you get around to following these instructions.

You write code into .elm files, which you can either preview dynamically or compile. You do the dynamic preview thing by running elm-server in your working directory. That starts up a server listening on http://localhost:8000 that automatically compiles or re-compiles any .elm file you request. That server runs on Happstack, and does a good enough job that the official elm-lang site seems to serve directly from it.

If you're like me though, you prefer to use static files for your actual front-end. You can use elm --make --minify [filename] to generate a working .html file[3] that you can serve up along with the elm-runtime from whatever application server you want to use.

Enough with the minutia though. Really, I'm here to give you a paragraph or two on what I think about the language.

What I think about the Language

The usual disclaimers apply.

  • you'll easily find more people who are familiar with JS/HTML than those who are familiar with Elm
  • if you use it, there's an extra[4] abstraction layer between you and the final front-end
  • using it forces your users to enable JavaScript. Ostensibly, you can use the compiler to generate noscript tags, but all these seem to do is statically document what the page would do if JS was on.

That second one in particular means that once again, you really should learn JavaScript before trying to use Elm to save yourself from it.

Once you get past that, it's quite beautiful and elegant. Much better than plain JS for some front-end work. Not that that's a very high bar.

There's some stuff conspicuously missing, like my beloved SSEs, and some basic DOM interactions including draggable and an arbitrary, element-triggered click event. The approaches available out-of-the-box are respectively, Drag Only One Element That You Can't Drop and Detect Mouse Location On A Click, Then Dispatch Based On It. Neither of those seem very satisfying. In fact, the proposed workarounds look strictly worse to me than the "callback hell" this language is trying to save me from.

Those shortcomings are just getting me more interested, to be honest. The reason being that it looks like it's possible to implement additional native functionality fairly easily, so all it'll do is cause me to spend some time writing up the appropriate, signal-based libraries to do these things.

Overall first impressions so far are good, though I'm seriously questioning how useful this language is going to be for more complicated interfaces. In the short term, I'll test out its shallow limits by writing a new WebMote front-end.

I'll let you know how it goes.


Footnotes

1 - [back] - Which I'm pretty sure will eventually be addressed. I particularly miss full sections and where, though you'd think the multi-line function declarations would be the biggest gap.

2 - [back] - For no reason I could see, : is Elm's type annotation operator, while :: is Elm's cons. It's precisely the opposite in Haskell, and buys little enough that I hereby formally question the decision. Similar reasoning seems to apply to the operator <|, which seems to do exactly the same thing as Haskells' $, except that it's twice as long.

3 - [back] - Or separate .html and .js files, if you also passed the -s flag.

4 - [back] - Not particularly stable, yet.