I would really love some data structure that is like a Record or a Dict, except instead of being keyed by some difficult-to-work-with compiler abstraction like a Record is, or keyed by a comparable with probably infinite options like a Dict (and then you can’t ever try to access a data structure without risking a key miss and falling into Maybe soup) it could be keyed by an additive type.
For example:
-- define an additive type which can also be keys to the new data structure
type Position = Left | Center | Right
-- define a new record/dict like data type keyed via the above ADT
-- how to actually initialize the thing is worth some discussion, I'm just treating it kind of like a record here.
coordinates =
{ Left = { x = -2.0, y = -0.5 }
, Center = { x = 0.0, y = 1.0 }
, Right = { x = 2.0, y = -0.5 }
}
-- define a key we might want to work with
currentPosition = Left
-- use key to get data.
-- compiler has *guarantee* that key exists in data structure so it will not require a Maybe result.
Debug.log "Right now I am at" (newDataStructure.get currentPosition coordinates)
-- having the keys in a list can help with dynamic iteration, impose order, etc
allPositions = [Left, Center, Right]
-- perform an operation that involves dynamically iterating over the keys of the structure
newCoordinates1UnitHigher =
List.foldl
(\ position newCoordinates ->
let
-- again, no maybe handling required
coordinate = NewDataStructure.get position coordinates
newCoordinate =
{ coordinate
| y = coordinate.y + 1
}
in
-- maybe updating a single field is handled with record-like syntax
{ newCoordinates
| position = newCoordinate
}
-- or maybe like Dict using an "update" method
NewDataStructure.update (\_ -> newCoordinate) newCoordinates
)
coordinates -- accumulator
allPositions -- list of keys
Now I have toyed with the idea of using a regular record and keying by its accessor functions, but this just creates a new set of problems.
1: you can’t simply test equality between keys.
– I tried to get around this by relying on a global constant record that had unique values at each key, then I could compare two variables containing keys by dereferencing the values in that global constant and comparing those instead.
– Interestingly, while a record accessor is a function and you can’t test equality on them (because they are functions), a Type example is also a function … … but the compiler is perfectly happy to allow you to test equality on those for some reason.
2: you really cannot iterate over the fields in a record, for example to alter each field and yield a new record, or to foldl over the data structure, or transform it. At least not via any mechanism I know short of just duplicating code for every possible key.
Many other languages — even those lacking full ADT support — still have something like Ruby’s atoms, which become very useful unique identifiers that are far more precise than strings but have similar self-documenting expressive power and one of the top things you’ll do with such a beast is to use them to as keys into hash/dict/record/struct/assoc-array/object/everyLanguageCallsTheseSomethingDifferent structures. And then you can have the key in a variable, and invoke the value keyed to what’s in that variable.
Elm ADT tags/constructors have a lot in common with Ruby atoms, and they are finite resources that the elm compiler has exhaustive knowledge of. So if you could devise a data type keyed by those, then the compiler could ensure that every tag in the type is used as a key and gets defined with a value, and later handle variables containing that ADT type and use those as dynamic keys into the data structure with absolute confidence that there will always be a value there, then that would offer a powerful ability that the language appears to presently lack while aligning with its core values.