lue-bird/elm-review-missing-record-field-lens/2.1.0

VariantHelper.GenerateUsed

Generate helpers for variant values

{ nameInModuleInternal : VariantHelperNameConfig
, nameInModuleExternal : VariantHelperNameConfig
, build : VariantHelperBuild
, generationModuleIsVariantModuleDotSuffix : String
}
-> 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:

There's no configuration to automatically import Variant.Module.Generation as Variant.Module because import aliases can't contain .

example 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"
}

example: 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

use it

... when you're using elm-accessors to mitigate boilerplate related to updating potentially deeply nested data.

don't use it

... when you consider accessors the less readable/intuitive/simple/explicit alternative.

build

type alias VariantHelperBuild =
{ variantModule : String
, typeName : String
, typeParameters : List String
, variantName : String
, variantValues : List TypeAnnotation
, otherVariants : Dict String { valueCount : Int }
}
-> { imports : List Import
, documentation : Maybe (Comment DocComment)
, annotation : Maybe TypeAnnotation
, implementation : Expression
}

Configure how to generate a variant helper declaration plus the necessary imports.

Out of the box, there are

Customize with

Create a custom helper generator (or just parts for replacement) with

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
        )
Comment DocComment
-> { declaration | documentation : Maybe (Comment DocComment) }
-> { declaration | documentation : Maybe (Comment DocComment) }

Build a different documentation:

accessorsDocumentedCustom info =
    accessors
        { valuesCombined = valuesRecord }
        info
        |> documented
            (emptyDocComment
                |> markdown
                    ("variant `" ++ info.variantName ++ "`: Accessor for the values.")
            )
TypeAnnotation
-> { declaration | annotation : Maybe TypeAnnotation }
-> { declaration | annotation : Maybe 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.

List Import
-> { declaration | imports : List Import }
-> { declaration | imports : List Import }

Supply additional imports required for generating the declaration.

accessorsAnnotatedOption : Build
accessorsAnnotatedOption info =
    accessors info
        |> annotated (typed "Option" [ ... ])
        |> importsAdd
            [ impostStmt [ "Accessors" ]
                Nothing
                ([ "Option" |> typeOrAliasExpose ] |> exposingExplicit |> Just)
            ]
{ name : String
, values : List TypeAnnotation
, valuesCombined : ValuesCombined
}
-> { access : Expression
, alter : Expression
, typeValues : 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
{ name : String
, values : List TypeAnnotation
, valuesCombined : ValuesCombined
}
-> { access : Expression
, alter : Expression
, typeValues : 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
type alias ValuesCombined =
{ name : String
, values : List TypeAnnotation
}
-> { typeInOne : TypeAnnotation
, inOne : String -> Expression
, patternInOne : String -> Pattern
, alter : Expression
}

Representation of values as one whole, for example

{ name : String
, values : List TypeAnnotation
}
-> { typeInOne : TypeAnnotation
, inOne : String -> Expression
, patternInOne : String -> Pattern
, alter : 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
{ name : String
, values : List TypeAnnotation
}
-> { typeInOne : TypeAnnotation
, inOne : String -> Expression
, patternInOne : String -> Pattern
, alter : 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 }
{ name : String
, values : List TypeAnnotation
}
-> 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

name

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 👀 ↓

Mini tip: testing is always a good idea for Parsers

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"

deprecated

onVariant : VariantHelperNameConfig

@deprecated in favor of variantAfter "on"

Handle helper names in the format on<Variant>.

Check out VariantHelperNameConfig for all naming options