The readme for Elm lazy states that is it deprecated because it is often confused with delayed computation, but I’ve a project for which I think it’s really required.
I’m writing an Apache Avro library for elm, which I’ve mentioned here before. The API is super neat, and the implementation is so much smaller and easier to think about than all the other languages I’ve seen.
But it gets tricky at the point where schemas in Avro can be mutually recursive by their names. Without mutation, building these from user defined data can be very challenging to make efficient. Laziness can provide just enough implicit mutation to make it possible.
Now what I want to write is a function which builds a map of names to their decoders, but these decoders lazily depend on each other.
Something like this:
buildNamedDecoders : List ( String, ResolvedSchema ) -> Dict String (Bytes.Decoder Value)
buildNamedDecoders namedPairs =
let
environment =
let
single ( name, schema ) =
let
recDec =
lazy (\_ -> makeBytesDecoder environment schema)
decoder =
Decode.lazy
(\_ -> force recDec)
in
( name, decoder )
in
List.map single namedPairs
|> Dict.fromList
in
environment
Calls to makeBytesDecoder
use the environment when they hit a named type.
But there are two issues which are stopping me.
- Elm’s recursive checks within let bindings are more strict than in modules (Let binding cyclic value detection is stronger than top level · Issue #2322 · elm/compiler · GitHub)
- The elm lazy library is deprecated.
Now the really nice bit about this implementation is that although makeBytesDecoder is actually quite costly in some cases, the resulting decoders absolutely are not, they’re almost completely linear.
Implementing without these changes requires building the environment map and decoder every time a named type is reached… every time a record is read, which makes it so much slower.
So what is the way to help shepherd changes like this? I’m happy to put in the work on the compiler and get the elm lazy module going again.