I am trying to extend http://package.elm-lang.org/packages/arturopala/elm-monocle/latest.
It provides "methods" that can be used to get and set fields within records. Like this:
lensFromWholeToPart.get {part = 9}
=> 9
lensFromWholeToPart.set 333 {part = 9}
=> {part = 333}In addition to the Lens type, Monocle also has Optional, which handles Maybe. The differences don't matter for this example, except that Optional's version of get is named getOption.
You can compose lenses to get new lenses that get and set deep within a data structure. You can also compose Optionals with Lenses, yielding new Optionals.
For concreteness, suppose I have a animal_editableCopy Optional that goes from an Animal to a field.
I also have an editableCopy_editableName Lens that goes from that field to one of its fields. Composing them
with Monocle looks like this:
animal_editedName = Optional.composeLens animal_editableCopy editableCopy_editableNameHowever, lenses don't have update or transform functions like this:
lensFromWholeToPart.update sqrt {part = 9}
=> {part = 3}That gets awkward when there are Maybe entries in the record. There's probably no generally
Right Thing to do when you update along a path that has a Nothing in it. For my application,
though, there is a right thing to do: any Nothing anywhere means that update doesn't change
the whole. So I've defined my own UpdatingLens and UpdatingOptional:
type alias UpdatingLens whole part =
{ get : whole -> part
, set : part -> whole -> whole
, update : (part -> part) -> whole -> whole
}It's easy and automatic to convert the get and set functions to an UpdatingLens:
lens getPart setPart =
{ get = getPart
, set = setPart
, update = lensUpdate getPart setPart
}
lensUpdate getPart setPart partTransformer whole =
setPart (whole |> getPart |> partTransformer) whole
The same is true for converting Monocle's Optional to my UpdatingOptional. The function that does that is called opt, and I'll use it below.
The problem at hand, then, is to write a composeLens that works with my types:
composeLens : UpdatingOptional whole part -> UpdatingLens part subpart -> UpdatingOptional whole subpartIt would be cool if my composeLens could be written like this:
composeLens left right =
let
composed = Optional.composeLens left right
in
opt composed.getOption composed.set
animal_editedName = composeLens animal_editableCopy editableCopy_editableName
That doesn't work, though:
The 2nd argument to function `composeLens` is causing a mismatch.
46| composeLens animal_editableCopy editableCopy_editableName
^^^^^^^^^^^^^^^^^^^^^^^^^
Function `composeLens` is expecting the 2nd argument to be:
{ ... }
But it is:
{ ..., update : ... }
Instead, it seems I have to explicitly "downcast" my types to Monocle's, compose those, and then add on to the result:
composeLens left right =
let
left_ = extractOptional left
right_ = extractLens right
composed = Optional.composeLens left_ right_
in
opt composed.getOption composed.set
extractLens : UpdatingLens whole part -> Lens whole part
extractLens u =
{ get = u.get
, set = u.set
}I believe the issue is that Monocle defines its functions like this:
composeLens : Optional a b -> Lens b c -> Optional a c
For the type checking to work, it'd have to define them as something like
composeLens : {left | getOption : ..., set : ...} -> { right | ... } -> ...I think. Not sure. But it looks as if, to use OO lingo, Optional is closed for both modification and extension.