Link Search Menu Expand Document

Under construction. This page is unfinished. Many headings just have some bullet points sketching the main points that should be discussed.

Consuming JS data

Elmish is explicitly built on top of React and does not try to hide this fact behind a higher-level abstraction. Elmish embraces React. This lets us leverage the ecosystem, but also comes with certain difficulties as we’re forced to pass data to and from JavaScript code, and JavaScript code can be very liberal with data shapes.

To make this data exchange saner and safer, Elmish provides a mechanism for data shape validation.

  1. Taking JS data as input
  2. Primitive types only
  3. Passing data out

Taking JS data as input

If you’re looking at a data structure you just got from some JavaScript code, represented as a Foreign of course (can’t make assumptions, can we?), you can use the readForeign function to see if it has the right shape:

import Elmish.Foreign (readForeign)

type MyData = { x :: { y :: Int }, z :: String }

callMeFromJavaScript :: Foreign -> String
callMeFromJavaScript f =
  case readForeign @MyData f of
    Nothing -> "Incoming data has the wrong shape"
    Just a -> "Got the right data: x.y = " <> show a.x.y <> ", z = " <> a.z

Try it live

readForeign will return Nothing if the value doesn’t conform to the expected type, or Just a if it does, where a has the right type.

NOTE: readForeign doesn’t actually convert the data structure. It only traverses it and makes sure that it has the right shape.

This strategy turns out to much faster than parsing with something like purescript-argonaut, which lets us use it at all kinds of ingest boundaries, such as:

  • Network API calls
  • Top-level entry points
  • Event handlers
  • etc.

The example above uses the readForeign function, which returns a Maybe, but it also has a big brother readForeign' (note the prime), which returns an Either, providing an error message about what exactly is incorrect with the data:

callMeFromJavaScript :: Foreign -> String
callMeFromJavaScript f =
  case readForeign' @MyData f of
    Left err -> "Oops: " <> err
    Right a -> "Got the right data: x.y = " <> show a.x.y <> ", z = " <> a.z

Try it live

NOTE: We’re using type wildcards (underscores) so we don’t have to write Either String every time.

Primitive types only

The fact that readForeign doesn’t actually convert data has an important corollary: the data structure cannot contain any types that do not have straightforward JavaScript representation.

For example, String is fine, and so it Array String, but Maybe String is not allowed, and neither is Either String Int.

This is by design. The idea behind this mechanism is to enable type safety, which is not the same as data marshalling, although the two are frequently conflated. In any given situation marshalling may or may not be appropriate. If it is, it can be done separately, in combination with readForeign or instead of it. If no marshalling is required, readForeign is much smaller and more performant.

Passing data out

  • Want to protect from accidentally passing PureScript data
  • So have a type-class
  • Compile-time only, zero-cost runtime