Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/lue-bird/elm-review-missing-record-field-lens

elm-review: helper generation
https://github.com/lue-bird/elm-review-missing-record-field-lens

accessors elm elm-review lens nested prism record-field variants

Last synced: 14 days ago
JSON representation

elm-review: helper generation

Awesome Lists containing this project

README

        

Despite what the name suggests,
this package contains _multiple_ [`elm-review`](https://package.elm-lang.org/packages/jfmengels/elm-review/latest/) rules to help with automatic code generation based on use:

- [`RecordFieldHelper.GenerateUsed`](RecordFieldHelper-GenerateUsed)
- [`VariantHelper.GenerateUsed`](VariantHelper-GenerateUsed)

When [`lue-bird/generate-elm`](https://github.com/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 ↓ ?

```elm
... 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:

```elm
import Field

... path newInput =
Field.projectsAlter
(Scroll.focusMap
(Fillable.fillMap
(Field.callsAlter
(List.map
(Tree.elementAlter
( path, Tree.childPrepend newInput )
)
)
)
)
)
```
with
```elm
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":

```elm
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":

```elm
import Field
import Emptiable.On
import Accessors exposing (over)
import Accessors.Library exposing (onEach)

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

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

→ [`RecordFieldHelper.GenerateUsed`](RecordFieldHelper.GenerateUsed) automatically generates _record field_ lenses you use.

In the last examples
- `projects`, `calls` lenses will be generated in `module Field`
- `Hand.On.filled` prism will be generated by [`VariantHelper.GenerateUsed`](VariantHelper-GenerateUsed)

## `RecordFieldHelper.GenerateUsed`

### try without installing

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

### configure

```elm
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`](RecordFieldHelper.GenerateUsed#Config)

### lenses that work out of the box

- [`erlandsona/elm-accessors`](https://package.elm-lang.org/packages/erlandsona/elm-accessors/latest/)
- [`sjorn3/elm-fields`](https://package.elm-lang.org/packages/sjorn3/elm-fields/latest/)
- [`arturopala/elm-monocle`](https://package.elm-lang.org/packages/arturopala/elm-monocle/latest)
- [`zh5/zipper`](https://package.elm-lang.org/packages/z5h/zipper/latest/)
- [`bChiquet/elm-accessors`](https://package.elm-lang.org/packages/bChiquet/elm-accessors/latest)

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`](VariantHelper-GenerateUsed#Config) below,
calling `YourVariantType.onOneOfThree`,
the rule will automatically
- `import YourVariantType.On`
- generate non-existent prisms/lenses `YourVariantType.On.variantName`

### try without installing

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

### configure

```elm
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`](VariantHelper-GenerateUsed#Config)!**

### out of the box

- [erlandsona/elm-accessors](https://package.elm-lang.org/packages/erlandsona/elm-accessors/latest)
- [bChiquet/elm-accessors](https://package.elm-lang.org/packages/bChiquet/elm-accessors/latest)

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
```elm
{ player : { position : ..., speed : ... }
, scene : { trees : ..., rocks : ... }
}
```
makes it unnecessarily hard to update inner fields.

organizing in blocks
```elm
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](https://youtu.be/x1FU3e0sT1I?t=1039)

```elm
{ 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](https://youtu.be/x1FU3e0sT1I?t=2745)

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`](https://package.elm-lang.org/packages/ianmackenzie/elm-3d-camera/latest).
Make sure to make types, packages, ... out of these.
Don't [obsessively employ primitives](https://elm-radio.com/episode/primitive-obsession/).

## suggestions?
→ [contributing](https://github.com/lue-bird/elm-review-missing-record-field-lens/blob/master/contributing.md)