Coder Social home page Coder Social logo

lue-bird / elm-review-missing-record-field-lens Goto Github PK

View Code? Open in Web Editor NEW
2.0 1.0 1.0 371 KB

elm-review: helper generation

Home Page: https://package.elm-lang.org/packages/lue-bird/elm-review-missing-record-field-lens/latest/

License: MIT License

JavaScript 3.51% Elm 96.49%
elm elm-review lens record-field accessors nested prism variants

elm-review-missing-record-field-lens's Introduction

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.


You find yourself writing code like ↓ ?

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

Field.nameAlter helpers will help remove some verbosity:

import Field

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

with

module Field exposing (callsAlter, projectsAlter)

callsAlter : (calls -> calls) -> { record | calls : calls } -> { record | calls : calls }
callsAlter alter =
    \record -> { record | calls = record.calls |> alter }
...

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

import Field
import Hand.On
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.On.filled --← a "lens" for the variant `Hand.Filled`
                (over Field.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 Emptiable.On
import Accessors exposing (over)
import Accessors.Library exposing (onEach)

... path newInput =
    over                           --                  <target>
        (Field.projects            -- { _ | projects : <Scroll ...> }
            << Scroll.focus
            << Emptiable.On.filled -- type Emptiable fill = Filled <fill> | ...
            << Field.calls         -- { _ | projects : <List ...> }
            << onEach              -- List (<Tree ...>)
            << Tree.elementAt path
        )
        (Tree.childPrepend newInput)

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

RecordFieldHelper.GenerateUsed automatically generates record field lenses you use.

In the last examples

RecordFieldHelper.GenerateUsed

try without installing

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

configure

module ReviewConfig exposing (config)

import RecordFieldHelper.GenerateUsed
import Review.Rule exposing (Rule)

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

See Config

lenses that work out of the box

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

VariantHelper.GenerateUsed

Helpers for the values of one variant.

With the Config below, calling YourVariantType.onOneOfThree, the rule will automatically

  • import YourVariantType.On
  • generate non-existent prisms/lenses YourVariantType.On.variantName

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 VariantHelper.GenerateUsed

config : List Rule
config =
    [ VariantHelper.GenerateUsed.rule
        { build =
            VariantHelper.GenerateUsed.accessors
                { valuesCombined = VariantHelper.GenerateUsed.valuesRecord }
        , nameInModuleInternal = VariantHelper.GenerateUsed.variantAfter "on"
        , nameInModuleExternal = VariantHelper.GenerateUsed.variant
        , generationModuleIsVariantModuleDotSuffix = "On"
        }
    ]

Check out Config!

out of the box

It's also possible to generate custom helpers 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 : ... }
}

makes it unnecessarily hard to update inner fields.

organizing in blocks

type alias Model = 
    { column : Column
    , textPage : TextPage
    }

often doesn't make sense in practice where small pieces interact with one another: from "Make Data Structures" by Richard Feldman – blocks → multiple sources of truth

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

Doesn't ↑ 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 etc. 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.

suggestions?

contributing

elm-review-missing-record-field-lens's People

Contributors

erlandsona avatar lue-bird avatar

Stargazers

 avatar  avatar

Watchers

 avatar

Forkers

erlandsona

elm-review-missing-record-field-lens's Issues

RE: VariantLens

2 thoughts

  1. rename: VariantLens.GenerateUsed -> NoMissingVariantPrism
    a. One because in Optics terms "Lenses" don't operate over Sum types / ADT's only "Product Types" aka records. Prisms operate over Sums/ADTS/Variants...
    b. To match better with NoMissingRecordFieldLens 🤷‍♂️

  2. Generate Prisms inline below the definition of the Variant to not force the defining module to expose its constructors.
    EG: I'd like to "expose" some lenses for operating over say pieces of an otherwise opaque record... something like...

module OpaqueThing exposing (Stuff, current)

type Stuff = Stuff Internal
-- generated opaque Prism constructor for composing helpers for accessing pieces of internal state.
onStuff : Lens Stuff Internal reach wrap
onStuff  = -- ...

type alias Internal = 
    { current_ : String
    , toggle : Bool  -- Notice callers have no access to this toggle property.
    } 

current : Lens Stuff String reach wrap
current = onStuff << Lens.current_

Simple self-contained correct example missing

Unfortunately I cannot get this review rule working. It always results in "I found no errors!". Here's my example code:

Main.elm

module Main exposing (Model, main)

import Browser
import Html exposing (Html, div, text)


type alias Model =
    { x : Int
    }


main : Program () Model Msg
main =
    Browser.sandbox { init = { x = 0 }, update = update, view = view }


type Msg
    = Increment
    | Decrement


update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            { model | x = model.x + 1 }

        Decrement ->
            { model | x = model.x - 1 }


view : Model -> Html Msg
view model =
    div []
        [ text <| String.fromInt model.x
        ]

review/src/ReviewConfig.elm

module ReviewConfig exposing (config)

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


config : List Rule
config =
    [ RecordFieldHelper.GenerateUsed.rule
        { generator = RecordFieldHelper.GenerateUsed.accessors
        , generateIn = ( "Lense", [] )
        }
    ]

Then I run npx elm-review --debug:

I found no errors!

I had expected that something gets generated in Lense.elm.

I also tried to create Lense.elm as an empty module beforehand, but didn't work either.

Could someone provide a simple self-contained correct example?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.