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

NOTE: the latest version is 2.1.0

VariantLens.GenerateUsed

Generate lenses for variant values

{ name : VariantLensNameConfig
, build : VariantLensBuild
, generationModuleIsVariantModuleDotSuffix : String
}
-> Rule

Generate prisms for variant types that are called from your code but aren't already defined in a dedicated module.

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


config : List Rule
config =
    [ VariantLens.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 =
    VariantLens.GenerateUsed.accessors
        { valuesRepresent = VariantLens.GenerateUsed.valuesRecord }
, name = VariantLens.GenerateUsed.prismNameVariant
, generationModuleIsVariantModuleDotSuffix = "On"
}

example: module Variant.Module.X exposing (onSome)

{ build =
    VariantLens.GenerateUsed.accessors
        { valuesRepresent = VariantLens.GenerateUsed.valuesRecord }
, name = VariantLens.GenerateUsed.prismNameOnVariant
, generationModuleIsVariantModuleDotSuffix = "X"
}

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 lenses the less readable/intuitive/simple/explicit alternative.

build

type alias VariantLensBuild =
{ 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 lens declaration plus the necessary imports.

Out of the box, there are

Customize with

Create a custom lens generator (parts) with

You can use the source code of accessors & co. as a starting point.

accessors : { valuesRepresent : ValuesRepresent } -> VariantLensBuild

VariantLensBuild of named erlandsona/elm-accessors which with

{ build =
    VariantLens.GenerateUsed.accessors
        { valuesRepresent = VariantLens.GenerateUsed.tupleNest }
, name = VariantLens.GenerateUsed.prismNameVariant
, 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 lens 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 : { valuesRepresent : ValuesRepresent } -> VariantLensBuild

VariantLensBuild of unnamed bChiquet/elm-accessors which with

{ build =
    VariantLens.GenerateUsed.accessorsBChiquet
        { valuesRepresent = VariantLens.GenerateUsed.tupleNest }
, name = VariantLens.GenerateUsed.prismNameVariant
, 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 lens 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
        { valuesRepresent = valuesRecord }
        info
        |> documented
            (emptyDocComment
                |> markdown
                    ("variant `" ++ info.variantName ++ "`: Accessor lens for the values.")
            )
TypeAnnotation
-> { declaration | annotation : Maybe TypeAnnotation }
-> { declaration | annotation : Maybe TypeAnnotation }

Build a different type annotation:

import Hand exposing (Hand(..))
import Stack


accessorsAnnotatedLens : VariantLensBuild
accessorsAnnotatedLens info =
    accessors
        { valuesRepresent = 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.

accessorsAnnotatedLens : VariantLensBuild
accessorsAnnotatedLens info =
    accessors info
        |> annotated (typed "Option" [ ... ])
        |> importsAdd
            [ impostStmt [ "Accessors" ]
                Nothing
                ([ "Option" |> typeOrAliasExpose ] |> exposingExplicit |> Just)
            ]
{ name : String
, values : List TypeAnnotation
, valuesRepresent : ValuesRepresent
}
-> { 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

variantAmongMultiple tupleNest

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
, valuesRepresent : ValuesRepresent
}
-> { 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 ValuesRepresent =
{ 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/variantAmongMultiple.

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/variantAmongMultiple.

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 lens name <=> variant name.

Out of the box, there are

You can also create a custom VariantLensNameConfig:

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 case of the results. They will be automatically be corrected when passed to rule.

prismNameVariant : VariantLensNameConfig

Handle lens names in the format on<Variant>. Check out VariantLensNameConfig for all naming options.

import Parser
import VariantLens.GenerateUsed


"success"
    |> Parser.run VariantLens.GenerateUsed.prismNameOnVariant.parser
--> { variantName = "Success" }


{ variantName = "Success" }
    |> VariantLens.GenerateUsed.prismOnVariant.build
--> "success"
prismNameOnVariant : VariantLensNameConfig

Handle lens names in the format on<Variant>. Check out VariantLensNameConfig for all naming options.

import Parser
import VariantLens.GenerateUsed


"onSuccess"
    |> Parser.run VariantLens.GenerateUsed.prismNameOnVariant.parser
--> { variantName = "Success" }


{ variantName = "Success" }
    |> VariantLens.GenerateUsed.prismOnVariant.build
--> "onSuccess"