Causing Bedlam in Elm

Below are lessons in breaking elm. Tongue in cheek (why u do dat, tho?), but illustrates interesting things about Elm to learn.

Elm is a china shop. I’m a bull. Let’s go!

Crashing Elm

Elm marketing:

No Runtime Exceptions

OH RLY?

In Ellie App, import Debug, and then paste this code in the increment update:

let
    big =
        List.range 1 9007199254740991
        |> List.map (\ _ -> List.range 1 9007199254740991)
    msg1 = Debug.log "Super star DJ's... HERE WE GO!" big
in

In Firefox, it’s polite and warns you the page is taking a long time.

However, if you open the developer tools, “Out of Memory”.

Chrome, it just up and crashes.

In Safari, thankfully it works like Steve Jobs said it works without Flash on the iPhone and is amazing performance and user experience! … meaning, you can sit there for 5 minutes and the page is still locked; no messages, just doing the best UX as possible and wasting your time while you sit there.

Lesson Learned: We should do bounds checking on our data sizes!

NO.

Er, Lesson Learned: We should disable Debug.log in our code by compiling Elm not in debug mode even if we use Debug.todo?

Yes. BUT NO!

Um… Lesson Learned?: Elm may be correct, but memory is limited. Use Elm types to mitigate unbounded data problems, offload to local AppStore/LocalCache, or use servers to help handle the immense data. Also, test early & often.

Crashing Elm. Again.

Elm marketing:

No Runtime Exceptions

OH RLY? Yo, lemme call my friend JavaScript from Elm, and here what they have to say about all dat.

JavaScript:

app.ports.yoJayEss.subscribe(function(message) {
  throw new Error('Sup, E...')
})

Call that port from Elm. Firefox shows the damage.

Lesson Learned: Write your JavaScript like a Functional Programmer would. Liberal use of try/catch, return Results or Promises from functions instead of throwing exceptions, and ensure when you define decoders in Elm from the JavaScript side, EVERYTHING IS A MAYBE because JavaScript is non-committal like that.

Blasphemous Pattern Matching

iS dIs NuMbEr A pOsItIvE iNt Or NoT?


hasBeerMoney val =
   case val of
       -1 ->
           False         
       _ ->
           True

lol, jk, that doesn’t compile.

Dude, negative numbers are numbers too.

Lesson Learned: You read this far just to see me troll Evan. In all seriousness, use the type system and functions to return meaningful types vs expecting low-level, non-meaningful primitives to pattern match. This kind of thinking leads to the Boolean Identity Crisis. Or use ReScript with React and their super-fun-to-debug useState hook. Side effects Crazy Town®? Bruh, it’s fine, you can now pattern match on negative ints.

Love You Forever And Ever And Ever ❤️!

Elm does recursion extremely well with some compiler support.

B U T!

loveYou name =
    loveYou name

That still has the halting problem and will freeze your browser.

Lesson Learned: Elm has built in recursion improvements for a variety of positive reasons, and you should think in recursion to solve looping problems. Use property/fuzz tests and bounds checking with early exit to prevent this from locking up your UI (i.e if > 9000, omg abort). If you’re algorithm is reasonable, but the data set is just gigantic, offload to a server instead for more horsepower. Or Workers if you don’t have server chops, can’t upload the large data, or don’t trust your server devs. (“But Doc, I’m the server dev!” Good joke. Ever̸y̴b̸ody laugh. Roll on s̵͓̆nâ̶̱re drum. Curtains.)

Conclusions

While I’ve only run into the memory problem during extreme Advent of Code which has extremely large data set sizes in later challenges, it’s clear it’s no one needs to worry about it in day to day work. Also, if you compile for production, Debug.log/Debug.todo goes away.

When using ports, how Elm talks to JavaScript and JavaScript talks to Elm, most people doing that already are writing pretty safe JavaScript. Where you get into trouble is the data marshaling; i.e. sending data to Elm from JavaScript. The decoders can easily fail because it’s easy to screw things up in JavaScript. It’s just all around safer, despite the royal pain of having a ton of Maybes you have to match on, to assume all data from JavaScript is sus.

For pattern matching, I always approached it with custom types and never thought to use low-level primitives. Coming from something like Ocaml or ReScript, I get the common assumption that Elm would “just work” how awesome it does in other FP languages that support pattern matching on primitive types. However, it doesn’t, and I feel like the type system being the common draw, that if you just use that, things like using negative primitives aren’t such a big deal. For those who’ve got battle scars converting types from database, back-end API, and domain logic… I empathize. I really do. Conversion & parsing is a royal pain. However, in Elm, it has a good payoff: things exhaustively work.

I have zero advice on the recursion beyond if you know if a reasonable hard limit, set it. This can make them painful to deal with because now you return a Result vs. a value. In most of the UI’s I deal with, we don’t have that much data. It’s typically paged by the UI anyway because of bandwidth constraints, ensuring you never get that high. With the influx of all these data scientists and ML people, at least in fintech however, I’ve started to see a lot of internal tools hit those limits, bandwidth be damned, because the data is clean, there, and useful. Without visual designers to create a reasonable experience around it with knowledge of these data size constraints, you can actually hit these types of limits that used to not be a problem in web UI (#inB41000RecordsInHTMLDataGrid).

Lastly, I didn’t cover it because the code for it is too mind numbing, but basically anything to do with Browser.Navigation, Tasks, or sequencing HTTP calls opens a world of hurt for race conditions. This kind of distributed networking is extremely hard to get right, even using formal method tools like TLA+/Alloy. It’s just easier to combine with automated testing via Cypress and manual testing… and as usual, really really thinking hard about how to model things using your types, even how things change over time.

Leave a Reply

Your email address will not be published. Required fields are marked *