Arc Forumnew | comments | leaders | submitlogin
Elm: FRP in the browser (elm-lang.org)
3 points by akkartik 3993 days ago | 2 comments


2 points by rocketnia 3989 days ago | link

Elm's examples make it easy to tweak the code and send it off to their server for immediate compilation, which is a pretty fun way to get started with FRP. :) I was able to follow the instructions to get Elm installed on my own computer too. I've encountered a lot of pain trying to set up Haskell GUI bindings, so this puts Elm at a distinct advantage relative to Haskell FRP libraries.

The Elm project has a lot in common with Flapjax, another FRP language. However, Flapjax automatically lifts expressions like "a + b" to return signals if the arguments are signals, whereas Elm just acts as a Haskell-like FRP library plus a Haskell-like language to use it in. In Elm, there's an explicit family of lift, lift2, lift3, ... operators for mapping an n-arg function over n Signals.

                                   Elm                   Flapjax
                          ---------------------   ---------------------
  Maintained in:           Haskell and some JS     Haskell and some JS
  Compiler runs in:              Haskell                   JS
  Application runs in:             JS                      JS
  Syntax approximates:           Haskell                   JS
  Expressions represent:         Values             Signals or Values
So far I've shied away from exploring Flapjax because its syntax is too deeply interlinked with JS. I'm not really sure how my synchronous-looking, side-effect-laden code will play out when some (but not all!) of its expressions represent time-varying values. Since Elm's syntax is more Haskell-like, I feel it can avoid inheriting JavaScript's quirks and be more faithful to the necessary semantics. If only Elm had a convenient syntax for transforming signals, such as Flapjax, GHC's Arrow syntax, or the arrow calculus.

Elm's interaction with JavaScript code seems like it could use some improvement, which matters because I care about using new Web standards as they come out. Fortunately, it's not so bad that it makes the language unusable for this purpose, and I have an idea for how to prototype a new FFI from within the existing one. I'll elaborate on that idea in a separate post.

-----

1 point by rocketnia 3989 days ago | link

Continuing...

"Elm's interaction with JavaScript code seems like it could use some improvement, which matters because I care about using new Web standards as they come out. Fortunately, it's not so bad that it makes the language unusable for this purpose, and I have an idea for how to prototype a new FFI from within the existing one. I'll elaborate on that idea in a separate post."

Since Elm's evaluation semantics are typical of functional languages, my design in this post could be relevant for just about any language that has an FFI to JavaScript. However, it's focused around what Elm already strives to make available in its FFI.

I've found four ways an Elm program can currently interact with JS code:

- There's a top-level way to associate well-behaved DOM events with Elm signals, described here: http://elm-lang.org/learn/Syntax.elm#javascript-ffi

- Some built-in JavaScript, JavaScript.Experimental, and Json modules are available, as described here: http://elm-lang.org/Documentation.elm

- The built-in functions == and show can operate on JavaScript values. There may be more like these.

- If a variable isn't bound in any imported module, the compiler will translate it directly into a JavaScript variable reference. This makes it possible to call JavaScript's eval() and similar tools. Unfortunately, this process is unhygienic, making it possible to access and assign to(?) the compiler-generated local variables. This feature isn't documented, but I've found no other use values of type JSString, JSFloat, etc., so I conclude it's the trap door to JS that I'm looking for.

When integrating a language like Elm with JS, some semantic awkwardness is to be expected. I don't expect Elm's reactivity to have perfectly elegant integration with all the step-by-step structure of the HTML+JS operational semantics. I don't expect an Elm `Element` to behave well in all the dark corner cases of DOM+CSS layout.

If it were up to me to improve Elm, I would stop relying on this unbound variable feature, since it's probably one of the oddest features as far as the type system is concerned. In place of this, I'd introduce an explicit eval mechanism, and I'd slightly redesign the other basic utilities to be more inclusive of heterogeneous Arrays, and generally to wrap JavaScript's idiosyncrasies more snugly.

As it turns out, I think this can be implemented as a library in Elm.

  module Js where
  
  -- We label something here as "unsafe" iff it makes it possible to
  -- break Elm's usual semantics even when using *safe* JavaScript
  -- techniques. This is meant to indicate that the JavaScript-related
  -- code needs to be extra careful to avoid breaking the Elm program.
  --
  -- For this definition to be meaningful, there has to be some notion
  -- of "safe" JavaScript code. We consider JavaScript code to be
  -- "unsafe" iff it defies JavaScript conventions in such a way that
  -- even an everyday JavaScript program would break. For instance, it's
  -- unsafe to modify basic platform features like Object.prototype and
  -- window.setTimeout. Ultimately, this is subjective; I'm relying on
  -- my own assessment of what an "everyday" JavaScript program is like.
  
  
  -- If a JavaScript Array or string has around 2^53 elements (and if we
  -- can somehow hold it in memory), the indexing functions may not work
  -- very well. Perhaps this issue will be more realistic if Elm ever
  -- supports infinite lists. Either way, I'm using the FixList and
  -- FixString types to make it clear that a list's length must be a
  -- representable integer.
  takeFix : Int -> [a] -> FixList a
  prependFix : FixList a -> [a] -> [a]
  type FixString = FixList Char
  
  
  -- These utilities are actually so safe that their behavior should be
  -- possible even if unsafe JavaScript code has utterly sabotaged
  -- everything else. If only the internal workings of the compiled Elm
  -- code were just as airtight! (Are they?)
  toFloat : JsValue -> Maybe Float
  fromFloat : Float -> JsValue
  toFiniteString : JsValue -> Maybe FixString
  fromFiniteString : FixString -> JsValue
  eq : JsValue -> JsValue -> Bool
  true : JsValue
  false : JsValue
  null : JsValue
  undefined : JsValue
  typeof : JsValue -> FixString
  
  
  -- I'm not sure there's any safe way to detect whether something is an
  -- Array and enumerate its contents. Some objects intentionally
  -- imitate Arrays, so this is a semantic battlefield.
  --
  -- Let's say fromArray checks for a "length" field and then enumerates
  -- numeric properties up to that length. This way it can handle
  -- arguments objects, strings, NodeLists, TouchLists, Uint32Arrays,
  -- jQuery objects, etc. Let's also say that any object that causes
  -- side effects during this operation is so unsafe for JavaScript that
  -- it's not extraordinarily unsafe for Elm.
  --
  fromArray : JsValue -> Maybe (FixList JsValue)
  toArray : FixList JsValue -> JsValue
  
  
  -- Execute arbitrary code as though by using Function( "..." )( x ).
  -- Naturally, the observed value of free variables, primitive-related
  -- prototypes, etc. may vary depending on the frame or Web worker used
  -- for executing the Elm code. If this JavaScript code has side
  -- effects or accesses internal details of Elm-managed objects, it may
  -- interfere with Elm's usual semantics.
  --
  -- Elm's design is oriented around pure manipulation of time-varying
  -- Signals, so significant JavaScript side effects should probably be
  -- reserved for responding to DOM events and Elm Signal updates,
  -- rather than acting at arbitrary moments during Elm's expression
  -- evaluation.
  --
  unsafeEvalFunctionBody : FiniteString -> JsValue -> JsValue
  
  
  -- I'm not sure there's a better alternative to "x instanceof Node" or
  -- "x instanceof Element" for detecting DOM nodes or DOM elements
  -- created in the JS code's frame. I'm not sure which of these Elm
  -- cares about, but let's say JavaScript code is unsafe for Elm if it
  -- causes these checks to fail or perform side effects, or if it gives
  -- a DOM node unusual behavior such as custom property getters.
  domNodeFromElement : Element -> JsValue
  unsafeDomNodeToElement : Int -> Int -> JsValue -> Maybe Element
  
  
  -- These take the place of the entire JavaScript.Experimental module
  -- and the Json module's utilities for JSObjects. I think there's no
  -- safe way to detect whether an object's "own" properties are all
  -- safe to dereference, so the JS-to-Elm conversions are unsafe.
  --
  -- Similarly, I'm not sure object and Array initialization are
  -- necessarily pure if Object.prototype or Array.prototype has been
  -- modified. In my testing, object and Array literals seem to be able
  -- to initialize fields without calling their setters, but it's not
  -- possible to use this technique with programmatic lengths/keys
  -- without resorting to some kind of eval. However, this case is
  -- unsafe for JavaScript code in general, so it isn't extraordinarily
  -- unsafe for Elm.
  --
  fromJson : JsonValue -> Maybe JsValue
  unsafeToJson : JsValue -> Maybe JsonValue
  fromNestedStructure : a -> Maybe JsValue
  unsafeToNestedStructure : JsValue -> Maybe a

-----