Skip to content

Native best practices

Noah edited this page Feb 16, 2016 · 11 revisions

Native is dangerous. It can introduce runtime exceptions into your code. For that reason, we have a set of best practices when writing Native code.

Safety

Defensive programming

Always use Decoders and Encoders!

You can bring things in from Native as Json.Value. This is better than bringing them in without

TODO: expand with examples

Use the least abstract type signature possible

Input types from Elm are compile-time checked, outputs from Native code is not. As a rule, always make sure that your function will return the same type for a given object.

The following function is fine, as the Elm code guarantees that the function will only receive an int.

var add = function(n){
  return n + n;
}; 
add : Int -> Int

The following function is not

add : a -> a

It will work fine for Int, String, Float, etc. But as soon as it's given any other type for which there is no + defined in Javascript, it will crash.

-- this will work fine
four = add 2

-- this too
nana = add "na"

-- this will kill the type system at _runtime_
oops = add Nothing

Kill null errors before they become errors

The following function is not okay

var select = function(query) {
  return document.querySelector(query);
};
select : String -> Node

The problem comes from the fact that document.querySelector returns null when it fails to find anything. Elm won't complain, as the Elm code will wrap the null with the type Node. The problem will come when you then try to use a Node in another function. You will get a null error.

-- no error here
nullNode : Node
nullNode = select "doesntexist"

-- this will die, and introduce a runtime exception
-- causing the runtime to die
content = getAttribute "content" nullNode

Instead, check for null and return a maybe.

var select = function(query) {
  var node = document.querySelector(query);
  if (node === null) return Maybe.Nothing;

  return Maybe.Just(node);
};
select : String -> Maybe Node

Conventions

Use always to ensure that your Tasks are wrapped appropriately.

See https://github.com/elm-lang/core/issues/453#issuecomment-161050745

Denote impure functions with Task

There's no real way outside of Tasks to signify when a function is impure in Elm. So, let's use Task everywhere!

activeElement : Task x QueryNode
activeElement =
    Native.Query.activeElement

Always import Native modules last

Don't do

import Native.MyNative
import MyOther

do

import MyOther
import Native.MyNative

The reason here is that occasionally, in some cases, if you use the incorrect import order, it will cause Elm to crash at runtime (albeit in a vocal way). We're currently unable to figure out why this happens, but it happens in this case here