Safe and explicit records constructors exploration

I agree that the proposed syntax additions here look like they might create some confusion. They don’t map neatly onto features in other languages I’m aware of, while also being “similar but different” to JS in a way that could be harmful.

It’s certainly true that this is something that comes up a lot and if we take an honest step back we probably all agree that there’s an underlying weakness in the Elm ecosystem here. The use cases for which Elm is primarily aimed aren’t brilliantly served by the current approach to JSON codecs. My current solution to the problem is to use the Elm IntelliJ plugin, which generates them for me. Adding language-level features to address this shortcoming is also an option and this could take the approach you outline above (new syntax or features to mitigate the current problems), or even new concepts that address the issue (in my day job I primarily use Scala, where the typical approach is to have the compiler generate the boiler plate of codecs).

Should we let tooling (e.g. editors) solve this problem, or is there a sense that error-prone and verbose codecs are one example of a weakness in Elm that needs addressing at the language level?

I confess that for the most part I am satisfied with Elm being a little more verbose and explicit than I’m used to in other languages, and the codec problem only feels different because:

  • JSON codecs are so ubiquitous
  • models evolve during development so the fragility of the current approach concretely causes hard-to-spot bugs

In the spirit of sharing experience from other areas I’d once again direct you to IntelliJ’s excellent codec generation feature.

The experience in Scala is also fairly pleasant (circe is a popular JSON library, other libraries take a similar approach):

import io.circe.generic.semiauto.{deriveEncoder, deriveDecoder}

case class Foo(n: Int, s: String)

val fooEncoder = deriveEncoder[Foo]
val fooDecoder = deriveDecoder[Foo]

These codecs are generated directly from the type definitions, so they are always correct. The underlying codecs are sensitive to order but because the compiler made them we know for sure that the order is correct.

Now you can use these elsewhere in the codebase (Scala often prefers methods over functions, that wouldn’t be the best approach in Elm):

import io.circe.syntax._
import io.circe.parser.parse

// returns String
Foo(1, "hi").asJson(fooEncoder)
// returns Either[<Error type>, Foo], same as Result in Elm
parse("""{"n": 1, "s": "example"}""").as[Foo](fooDecoder)

My sense is that introducing this power into the language would be a big change for Elm, but if it’s possible to do this in tooling then this may be easier to adopt. What if there was a code generation step in Elm’s ecosystem? Should this be a part of the compiler? A separate command? Something else? What other similar problems would be solved with this power?

the root cause of these issues is actually the signature of records type aliases constructors

I guess this is the bit I disagree with! Given Elm as it currently is, changing record constructors could mitigate the problem of JSON codecs, but if we look deeper there are other approaches available and it is worth thinking about what we are trying to solve, and what trade-offs we’re prepared to make in doing so.
If the existing IntelliJ (or the myriad online generators) approach of Java-style code generation in the editor is enough by itself then great!
Solving this problem in the language would mean making Elm more powerful. I think it’s a challenge to find a way to do this that doesn’t do one or more of:

  • increase the complexity of the syntax
  • add a new concept that needs to be internalised
  • create a new powerful feature that can be (ab)used elsewhere in the language