Hi Richard,
this is not code that is in production yet and rather experimental, but I
still think it might be worth sharing in the context of this thread since I
had to write painful code to update nested records there (unfortunately,
that code was never commited anywhere so I cannot share the ugly version,
but I think you might be able to envision it given the model and the
current code). I then came across an article by Wouter
<https://medium.com/elm-shorts/updating-nested-records-in-elm-15d162e80480#.orggcyb9e>
addressing
the topic of updating nested records and I tried to apply it to my use case.
Let's start with the model that represents a series of paragraphs and a
text selection:
type alias Model =
{ paras : Dict Int Para
, selection : Maybe Selection
}
type alias Para =
{ text : String }
type alias Selection =
{ cursorCoordinate : TextCoordinate
, anchorCoordinate : TextCoordinate -- not in use yet
}
type alias TextCoordinate =
{ textPos : TextPosition
, location : Location
}
type alias TextPosition =
{ iPara : Int
, offset : Int
}
type Location
= Before
| After
I am aware that there is probably a lot of room for improvement for more
experienced Elm developers, but here is the "best way" I could come up with
for an update method that "moves" a selection to the left (think "arrow key
left" is pressed by the user):
dismantle : TextCoordinate -> ( Int, Int, Location )
dismantle tc =
( tc.textPos.offset, tc.textPos.iPara, tc.location )
moveLeft : Model -> TextCoordinate -> Maybe Selection
moveLeft model ({ textPos } as cursorCoordinate) =
let
( offsetCur, iParaCur, locationCur ) =
dismantle cursorCoordinate
paraCur =
Dict.get iParaCur model.paras
cursorCoordinateNew =
case paraCur of
Nothing ->
-- this should never happen, since we should have a
valid text coordinate at that point
cursorCoordinate
Just para ->
let
paraLength =
String.length para.text
iParaAbove =
iParaCur - 1
offsetNew =
(offsetCur - 1)
|> cropTo -1 (paraLength - 1)
in
if (offsetNew < 0) then
if (paraLength > 0 && locationCur == After) then
-- we moved "after -1st" character:
normalize to "before 0st"
textPos
|> setOffset 0
|> asTextPosIn cursorCoordinate
|> setLocation Before
else
-- we moved "before -1st" character or we
are in an empty para:
-- => normalize to "after last of prev.
para"
let
mbParaAbove =
Dict.get iParaAbove model.paras
in
case mbParaAbove of
Nothing ->
textPos
|> setOffset 0
|> setIPara 0
|> asTextPosIn
cursorCoordinate
|> setLocation Before
Just paraAbove ->
let
paraAboveLength =
String.length
paraAbove.text
offsetAbove =
paraAboveLength |>
cropTo 0 paraAboveLength
in
textPos
|> setOffset offsetAbove
|> setIPara iParaAbove
|> asTextPosIn
cursorCoordinate
|> setLocation After
else
textPos
|> setOffset offsetNew
|> asTextPosIn cursorCoordinate
in
createSelection cursorCoordinateNew
To make this work, I've written the "set***" and "as***In" methods as
Wouter suggests in his article to complement my "model-API":
setSelection : Maybe Selection -> Model -> Model
setSelection selectionNew modelCur =
{ modelCur | selection = selectionNew }
asSelectionIn : Model -> Maybe Selection -> Model
asSelectionIn =
flip setSelection
setParas : Dict Int Para -> Model -> Model
setParas parasNew modelCur =
{ modelCur | paras = parasNew }
asParasIn : Model -> Dict Int Para -> Model
asParasIn =
flip setParas
setText : String -> Para -> Para
setText textNew paraCur =
{ paraCur | text = textNew }
asTextIn : Para -> String -> Para
asTextIn =
flip setText
setCursorCoordinate : TextCoordinate -> Selection -> Selection
setCursorCoordinate cursorCoordinateNew selectionCur =
{ selectionCur | cursorCoordinate = cursorCoordinateNew }
asCursorCoordinateIn : Selection -> TextCoordinate -> Selection
asCursorCoordinateIn =
flip setCursorCoordinate
setAnchorCoordinate : TextCoordinate -> Selection -> Selection
setAnchorCoordinate anchorCoordinateNew selectionCur =
{ selectionCur | anchorCoordinate = anchorCoordinateNew }
asAnchorCoordinateIn : Selection -> TextCoordinate -> Selection
asAnchorCoordinateIn =
flip setAnchorCoordinate
setTextPos : TextPosition -> TextCoordinate -> TextCoordinate
setTextPos textPosNew textCoordinateCur =
{ textCoordinateCur | textPos = textPosNew }
asTextPosIn : TextCoordinate -> TextPosition -> TextCoordinate
asTextPosIn =
flip setTextPos
setLocation : Location -> TextCoordinate -> TextCoordinate
setLocation locationNew textCoordinateCur =
{ textCoordinateCur | location = locationNew }
asLocationIn : TextCoordinate -> Location -> TextCoordinate
asLocationIn =
flip setLocation
setIPara : Int -> TextPosition -> TextPosition
setIPara iParaNew textPosCur =
{ textPosCur | iPara = iParaNew }
asIParaIn : TextPosition -> Int -> TextPosition
asIParaIn =
flip setIPara
setOffset : Int -> TextPosition -> TextPosition
setOffset offsetNew textPosCur =
{ textPosCur | offset = offsetNew }
asOffsetIn : TextPosition -> Int -> TextPosition
asOffsetIn =
flip setOffset
I hope this somehow helps to improve on the record update syntax. I'd love
to have a possibility to not have to write above boilerplate in the future
:)
All credits for the above API goes to Wouter!
Best,
Robert
On Friday, March 3, 2017 at 7:12:39 AM UTC+1, Richard Feldman wrote:
>
> There have been various discussions of potential ways to improve Elm's
> record update syntax. Evan commented that "(examples > design work) at this
> point" - any potential designs for syntax improvements would need to be run
> through a gauntlet of examples to see how well they'd work, so the first
> step in the process is to gather those examples.
>
> So let's collect a ton of different real-world examples! That will help
> guide the design process.
>
> If you've run into a record update that you felt was painful and could be
> improved in some way, please post it here! (Also, *please keep this
> thread for posting of examples* *only* - it'll be easier to link back
> here during design discussions if we can reference a clean thread of
> examples, as opposed to a mismash of examples interleaved with suggestions.)
>
--
You received this message because you are subscribed to the Google Groups "Elm
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.