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.

Reply via email to