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
projects
,calls
lenses will be generated inmodule Field
Hand.On.filled
prism will be generated byVariantHelper.GenerateUsed
elm-review --template lue-bird/elm-review-missing-record-field-lens/example/field-accessors
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
It's also possible to generate custom helpers or to customize the generation of existing ones.
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
elm-review --template lue-bird/elm-review-missing-record-field-lens/example/variant-accessors
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
!
It's also possible to generate custom helpers or to customize the generation of existing ones.
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 parts are logically connected like an Address
or a Camera
.
Make sure to make types, packages, ... out of these.
Don't obsessively employ primitives.