Generate helpers for variant values
rule : { nameInModuleInternal : VariantHelperNameConfig, nameInModuleExternal : VariantHelperNameConfig, build : VariantHelperBuild, generationModuleIsVariantModuleDotSuffix : String } -> Review.Rule.Rule
Generate each helper for a variant of a type
that is called from your code but isn't already defined.
import Review.Rule as Rule exposing (Rule)
import VariantHelper.GenerateUsed
config : List Rule
config =
[ VariantHelper.GenerateUsed.rule ..config..
]
..config.. How to generate, where to generate:
build :
a Build
function like
name :
a way to handle variant helper names like
generationModuleIsVariantModuleDotSuffix :
a .Suffix
to derive generation module
names from variant module
namesThere's no configuration to automatically import Variant.Module.Generation as Variant.Module
because import
aliases can't contain .
module Variant.Module.On exposing (some)
{ build =
VariantHelper.GenerateUsed.accessors
{ valuesCombined = VariantHelper.GenerateUsed.valuesRecord }
, nameInModuleInternal = VariantHelper.GenerateUsed.variant
, nameInModuleExternal = VariantHelper.GenerateUsed.variantAfter "on"
, generationModuleIsVariantModuleDotSuffix = "On"
}
module Variant.Module.X exposing (onSome)
{ build =
VariantHelper.GenerateUsed.accessors
{ valuesCombined = VariantHelper.GenerateUsed.valuesRecord }
, nameInModuleInternal = VariantHelper.GenerateUsed.variantAfter "on"
, nameInModuleExternal = VariantHelper.GenerateUsed.variantAfter "on"
, generationModuleIsVariantModuleDotSuffix = "X"
}
Variant helpers will be generated below the type
declaration.
Consider SiriusStarr/elm-review-no-unsorted
for more fine-grained and consistent positioning control
... when you're using elm-accessors
to mitigate
boilerplate related to updating potentially deeply nested data.
... when you consider accessors the less readable/intuitive/simple/explicit alternative.
{ variantModule : String
, typeName : String
, typeParameters : List String
, variantName : String
, variantValues : List Elm.CodeGen.TypeAnnotation
, otherVariants : Dict String { valueCount : Basics.Int } } -> { imports : List Elm.CodeGen.Import
, documentation : Maybe (Elm.CodeGen.Comment Elm.CodeGen.DocComment)
, annotation : Maybe Elm.CodeGen.TypeAnnotation
, implementation : Elm.CodeGen.Expression
}
Configure
how to generate a variant helper declaration
plus the necessary import
s.
Out of the box, there are
Customize with
Create a custom helper generator (or just parts for replacement) with
the-sett/elm-syntax-dsl
variantInMultiple
, variantOnly
valuesTupleNest
, valuesRecord
variantPattern
You can use the source code of accessors
& co. as a starting point.
accessors : { valuesCombined : ValuesCombined } -> VariantHelperBuild
Build
of named erlandsona/elm-accessors
which with
{ build =
VariantHelper.GenerateUsed.accessors
{ valuesCombined = VariantHelper.GenerateUsed.valuesTupleNest }
, nameInModuleInternal = VariantHelper.GenerateUsed.variantAfter "on"
, nameInModuleExternal = VariantHelper.GenerateUsed.variant
, generationModuleIsVariantModuleDotSuffix = "On"
}
and
module Data exposing (Data(..))
type Data a b c d
= Some a b c d
| None
generates
module Data.On exposing (some)
import Accessors exposing (makeOneToN_)
import Data exposing (Data(..))
{-| Accessor prism for the variant `Data.Some` of the `type Data`.
-}
some :
Relation ( a, ( b, ( c, d ) ) ) reachable wrap
-> Relation (Data a b c d) reachable (Maybe wrap)
some =
makeOneToN_
"Data.Some"
(\valuesAlter variantType ->
case variantType of
Some value0 value1 value2 value3 ->
( value0, ( value1, ( value2, value3 ) ) ) |> valuesAlter |> Just
_ ->
Nothing
)
(\valuesAlter variantType ->
case variantType of
Some value0 value1 value2 value3 ->
let
( alteredValue0, ( alteredValue1, ( alteredValue2, alteredValue3 ) ) ) =
( value0, ( value1, ( value2, value3 ) ) ) |> valuesAlter
in
Some alteredValue0 alteredValue1 alteredValue2 alteredValue3
other ->
other
)
accessorsBChiquet : { valuesCombined : ValuesCombined } -> VariantHelperBuild
Build
of unnamed bChiquet/elm-accessors
which with
{ build =
VariantHelper.GenerateUsed.accessorsBChiquet
{ valuesCombined = VariantHelper.GenerateUsed.valuesTupleNest }
, nameInModuleInternal = VariantHelper.GenerateUsed.variant
, nameInModuleExternal = VariantHelper.GenerateUsed.variantAfter "on"
, generationModuleIsVariantModuleDotSuffix = "On"
}
and
module Data exposing (Data(..))
type Data a b c d
= Some a b c d
| None
generates
module Data.On exposing (some)
import Accessors exposing (Lens, Relation, makeOneToN_)
import Data exposing (Data(..))
{-| Accessor prism for the variant `Data.Some` of the `type Data`.
-}
some :
Relation ( a, ( b, ( c, d ) ) ) reachable wrap
-> Relation (Data a b c d) reachable (Maybe wrap)
some =
makeOneToN
(\valuesAlter variantType ->
case variantType of
Some value0 value1 value2 value3 ->
( value0, ( value1, ( value2, value3 ) ) ) |> valuesAlter |> Just
_ ->
Nothing
)
(\valuesAlter variantType ->
case variantType of
Some value0 value1 value2 value3 ->
let
( alteredValue0, ( alteredValue1, ( alteredValue2, alteredValue3 ) ) ) =
( value0, ( value1, ( value2, value3 ) ) ) |> valuesAlter
in
Some alteredValue0 alteredValue1 alteredValue2 alteredValue3
other ->
other
)
documented : Elm.CodeGen.Comment Elm.CodeGen.DocComment -> { declaration | documentation : Maybe (Elm.CodeGen.Comment Elm.CodeGen.DocComment) } -> { declaration | documentation : Maybe (Elm.CodeGen.Comment Elm.CodeGen.DocComment) }
Build a different documentation:
accessorsDocumentedCustom info =
accessors
{ valuesCombined = valuesRecord }
info
|> documented
(emptyDocComment
|> markdown
("variant `" ++ info.variantName ++ "`: Accessor for the values.")
)
annotated : Elm.CodeGen.TypeAnnotation -> { declaration | annotation : Maybe Elm.CodeGen.TypeAnnotation } -> { declaration | annotation : Maybe Elm.CodeGen.TypeAnnotation }
Build a different type annotation:
import Hand exposing (Hand(..))
import Stack
accessorsAnnotatedOption : Build
accessorsAnnotatedOption info =
accessors
{ valuesCombined = valuesRecord }
info
|> annotated
(typed "Option"
[ CodeGen.typed info.typeName
(info.typeParameters |> List.map CodeGen.typeVar)
, case variantValues |> Stack.fromList of
Empty _ ->
CodeGen.unitAnn
Filled stacked ->
Filled stacked
|> Stack.reverse
|> Stack.fold (\value soFar -> CodeGen.tupleAnn [ value, soFar ])
, CodeGen.typeVar "reachable"
, CodeGen.typeVar "wrap"
]
)
|> importsAdd
[ impostStmt [ "Accessors" ]
Nothing
([ "Option" |> typeOrAliasExpose ] |> exposingExplicit |> Just)
]
Make sure to importsAdd
.
importsAdd : List Elm.CodeGen.Import -> { declaration | imports : List Elm.CodeGen.Import } -> { declaration | imports : List Elm.CodeGen.Import }
Supply additional import
s required for generating the declaration.
accessorsAnnotatedOption : Build
accessorsAnnotatedOption info =
accessors info
|> annotated (typed "Option" [ ... ])
|> importsAdd
[ impostStmt [ "Accessors" ]
Nothing
([ "Option" |> typeOrAliasExpose ] |> exposingExplicit |> Just)
]
variantInMultiple : { name : String, values : List Elm.CodeGen.TypeAnnotation, valuesCombined : ValuesCombined } -> { access : Elm.CodeGen.Expression, alter : Elm.CodeGen.Expression, typeValues : Elm.CodeGen.TypeAnnotation }
Helpers for values of a given variant among >= 2.
for
type Data a b c d
= Some a b c d
| None
with
variantInMultiple valuesTupleNest
access
\valuesAlter variantType ->
case variantType of
Some value0 value1 value2 value3 ->
( value0, ( value1, ( value2, value3 ) ) ) |> valuesAlter |> Just
_ ->
Nothing
alter
\valuesAlter variantType ->
case variantType of
Some value0 value1 value2 value3 ->
let
( alteredValue0, ( alteredValue1, ( alteredValue2, alteredValue3 ) ) ) =
( value0, ( value1, ( value2, value3 ) ) ) |> valuesAlter
in
Some alteredValue0 alteredValue1 alteredValue2 alteredValue3
other ->
other
variantOnly : { name : String, values : List Elm.CodeGen.TypeAnnotation, valuesCombined : ValuesCombined } -> { access : Elm.CodeGen.Expression, alter : Elm.CodeGen.Expression, typeValues : Elm.CodeGen.TypeAnnotation }
Helpers for values of a given only variant.
for
type Id attachment
= Id (List Int) attachment
access
\(Id value0 value1) -> ( value0, value1 )
alter
\valuesAlter (Id value0 value1) ->
let
( alteredValue0, alteredValue1 ) =
( value0, value1 ) |> valuesAlter
in
Id alteredValue0 alteredValue1
{ name : String
, values : List Elm.CodeGen.TypeAnnotation } -> { typeInOne : Elm.CodeGen.TypeAnnotation
, inOne : String -> Elm.CodeGen.Expression
, patternInOne : String -> Elm.CodeGen.Pattern
, alter : Elm.CodeGen.Expression
}
Representation of values as one whole, for example
valuesRecord
: { value0 = ..., value1 = ..., value2 = ... }
valuesTupleNest
: ( ..., ( ..., ... ) )
Toop.T3 ... ... ...
valuesTupleNest : { name : String, values : List Elm.CodeGen.TypeAnnotation } -> { typeInOne : Elm.CodeGen.TypeAnnotation, inOne : String -> Elm.CodeGen.Expression, patternInOne : String -> Elm.CodeGen.Pattern, alter : Elm.CodeGen.Expression }
Helpers for a given variant to use with a custom implementation or variantOnly
/variantInMultiple
.
for
type Data a b c d
= Some a b c d
| None
typeInOne
( a, ( b, ( c, d ) ) )
inOne "value"
( value0, ( value1, ( value2, value3 ) ) )
patternInOne "value"
( value0, ( value1, ( value2, value3 ) ) )
alter
let
( alteredValue0, ( alteredValue1, ( alteredValue2, alteredValue3 ) ) ) =
( value0, ( value1, ( value2, value3 ) ) ) |> valuesAlter
in
Some alteredValue0 alteredValue1 alteredValue2 alteredValue3
valuesRecord : { name : String, values : List Elm.CodeGen.TypeAnnotation } -> { typeInOne : Elm.CodeGen.TypeAnnotation, inOne : String -> Elm.CodeGen.Expression, patternInOne : String -> Elm.CodeGen.Pattern, alter : Elm.CodeGen.Expression }
Helpers for a given variant to use with a custom implementation or variantOnly
/variantInMultiple
.
for
type Data a b c d
= Some a b c d
| None
access
{ value0 = value0, value1 = value1, value2 = value2, value3 = value3 }
alter
let
altered =
{ value0 = value0, value1 = value1, value2 = value2, value3 = value3 }
|> valuesAlter
in
Some altered.value0 altered.value1 altered.value2 altered.value3
typeValues
{ value0 = a, value1 = b, value2 = c, value3 = d }
variantPattern : { name : String, values : List Elm.CodeGen.TypeAnnotation } -> Elm.CodeGen.Pattern
Pattern on a given variant to use with a custom implementation.
for
type Data a b c d
= Some a b c d
| None
generates
Some value0 value1 value2 value3
RecordWithoutConstructorFunction { parser : Parser { variantName : String }
, build : { variantName : String } -> String
}
How to derive helper name ↔ variant name.
Out of the box, there are
You can also create a custom VariantHelperNameConfig
:
import Parser
{ build = \{ variantName } -> variantName ++ "Variant"
, parser =
Parser.map (\variantName -> { variantName = variantName })
(Parser.loop ""
(\beforeSuffixFoFar ->
Parser.oneOf
[ Parser.token "Variant"
|. Parser.end
|> Parser.map (\() -> Parser.Done beforeSuffixFoFar)
, Parser.chompIf (\_ -> True)
|> Parser.getChompedString
|> Parser.map
(\stillNotSuffix ->
Parser.Loop (beforeSuffixFoFar ++ stillNotSuffix)
)
]
)
)
}
It's not half as daunting as it looks. If you feel motivated 👀 ↓
elm/parser
packageMini tip: testing is always a good idea for Parser
s
Don't worry about the casing of the results.
They will be automatically be corrected when passed to rule
.
In the future,
elm-morph
will allow creating builders and parsers in one go,
making this easier.
variantAfter : String -> VariantHelperNameConfig
Handle helper names in the format prefix<Variant>
.
Check out VariantHelperNameConfig
for all naming options.
import Parser
import VariantHelper.GenerateUsed
"toSuccess"
|> Parser.run
(VariantHelper.GenerateUsed.variantAfter "to"
|> .parser
)
--> { variantName = "Success" }
{ variantName = "Success" }
|> (VariantHelper.GenerateUsed.variantAfter "map"
|> .build
)
--> "mapSuccess"
variant : VariantHelperNameConfig
Handle helper names in the format <variant>
.
Check out VariantHelperNameConfig
for all naming options.
import Parser
import VariantHelper.GenerateUsed
"success"
|> Parser.run VariantHelper.GenerateUsed.helperNameAsVariant.parser
--> { variantName = "Success" }
{ variantName = "Success" }
|> VariantHelper.GenerateUsed.helperNameAsVariant.build
--> "success"
onVariant : VariantHelperNameConfig
@deprecated in favor of
variantAfter "on"
Handle helper names in the format on<Variant>
.
Check out VariantHelperNameConfig
for all naming options