/elm-review-missing-record-field-lens

elm-review rule: generate record field lenses that don't exist yet

Primary LanguageElmMIT LicenseMIT

Despite what the name suggests, this package contains multiple elm-review rules to help with automatic code generation based on use:

When lue-bird/generate-elm – a framework for making code generation easy and safe – is finished, every functionality will be ported over.

NoMissingRecordFieldLens

You find myself writing code like this?

... path newInput =
    \state ->
        { state
            | projects =
                state.projects
                    |> Scroll.focusMap
                        (Fillable.map
                            (\project ->
                                { project
                                    | calls =
                                        project.calls
                                            |> List.map
                                                (Tree.elementAlter
                                                    ( path, Tree.childPrepend newInput )
                                                )
                                }
                            )
                        )
        }

Let's define some Field.nameAlter helpers:

module Field exposing (callsAlter, projectsAlter)

then

import Field

... path newInput =
    Field.projectsAlter
        (Scroll.focusMap
            (Fillable.fillMap
                (Field.callsAlter
                    (List.map
                        (Tree.elementAlter
                            ( path, Tree.childPrepend newInput )
                        )
                    )
                )
            )
        )

We can reduce the number of helpers by combining the possible operations (access, replace, alter, name, ...) into a "lens":

import Field
import Accessors exposing (over)
import Accessors.Library exposing (onEach)

... path newInput =
    over Field.projects --← a "lens" for the field .projects
        (over Scroll.focus
            (over Hand.onFilled
                (over Fields.calls --← a "lens" for the field .calls
                    (over onEach
                        (over (Tree.elementAt path)
                            (Tree.childPrepend newInput)
                        )
                    )
                )
            )
        )

Seeing a pattern? You can, to put the cherry on the cake, compose those "lenses":

import Field
import Accessors exposing (over)
import Accessors.Library exposing (onEach)

... path newInput =
    over
        ((Field.projects << Scroll.focus << Hand.onFilled)
            << Field.calls
            << onEach
            << Tree.elementAt path
        )
        (Tree.childPrepend newInput)

Methods like this make your code more readable. Compare with the first example.

NoMissingRecordFieldLens automatically generates record field lenses you use. No more manual labour.

In the last examples, Field.projects & Field.calls will be generated in Field.elm.

try without installing

elm-review --template lue-bird/elm-review-missing-record-field-lens/example/field-accessors

configure

module ReviewConfig exposing (config)

import NoMissingRecordFieldLens
import Review.Rule exposing (Rule)

config : List Rule
config =
    [ NoMissingRecordFieldLens.rule
        { generator = NoMissingRecordFieldLens.accessors
        , generateIn = ( "Field", [] )
        }
    ]

See Config

lenses that work out of the box

It's also possible to generate custom lenses or to customize the generation of existing ones.

pitfalls

Don't let this pattern warp you into overusing nesting.

Structuring a model like

{ player : { position : ..., speed : ... }
, scene : { trees : ..., rocks : ... }
}

is a smelly pattern. It makes it unnecessarily hard to update inner fields.

{ playerPosition : ...
, playerSpeed : ...
, sceneTrees : ...
, sceneRocks : ...
}

Doesn't this make ui harder? Yes, but the extra explicitness is worth it. player could have things that are irrelevant to the ui like configuredControls etc. It's best to keep state structure and ui requirements separate.

Similarly, leaning towards a more limited, domain tailored API of types, packages, ... with strong boundaries will lead to easier code with stronger guarantees. ↑ example from "Make Data Structures" by Richard Feldman: Doc.id should be read-only

Don't try to design your API around lenses. Only if the API interaction happens to mirror that behavior, Dōzo!

when is nesting acceptable?

When parts are logically connected like an Address or a Camera. Make sure to make types, packages, ... out of these. Don't obsessively employ primitives.

VariantPrism.GenerateUsed

The motivations for using this are similar to NoMissingRecordFieldLens, this time trying to cut down on situations where you're only interested in values of one variant.

For any variant type, you can call YourVariantType.On.oneOfThree. If this prism hasn't already been created, it will automatically be generated.

try without installing

elm-review --template lue-bird/elm-review-missing-record-field-lens/example/variant-accessors

configure

module ReviewConfig exposing (config)

import Review.Rule as Rule exposing (Rule)
import VariantPrism.GenerateUsed

config : List Rule
config =
    [ { name = VariantPrism.GenerateUsed.prismNameOnVariant
      , build = VariantPrism.GenerateUsed.accessors
      }
        |> VariantPrism.GenerateUsed.inVariantOriginModuleDotSuffix
            "Extra.Local"
        |> VariantPrism.GenerateUsed.importGenerationModuleAsOriginModule
        |> VariantPrism.GenerateUsed.rule
    ]

Check out Config!

prisms that work out of the box

It's also possible to generate custom lenses or to customize the generation of existing ones.

suggestions?

contributing.