mdgriffith/elm-codegen/6.0.2

NOTE: the latest version is 6.0.3

Elm.Declare

You may run into situations where you want to generate a function, and then call that generated function somewhere else.

This module will help you do that.

Here's an example, let's define a new function called add42

import Elm.Arg as Arg
import Elm.Declare as Declare


renderFile =
    let
        add42 =
            Declare.fn "add42"
                (Arg.value "firstInt")
                (\firstArgument ->
                    Elm.plus
                        (Elm.int 42)
                        firstArgument
                )
    in
    Elm.file [ "MyFile" ]
        -- add our declaration to our file
        [ add42.declaration


        -- and another place where we call that function!
        , Elm.declaration "mySweetNumber"
            (add42.call (Elm.int 82))
        ]
type alias Function tipe =
{ call : tipe
, value : Expression
, declaration : Declaration
, internal : Internal tipe
}
fn :
String
-> Arg value
-> (value -> Expression)
-> Function (Expression -> Expression)
fn2 :
String
-> Arg one
-> Arg two
-> (one -> two -> Expression)
-> Function (Expression -> Expression -> Expression)
fn3 :
String
-> Arg one
-> Arg two
-> Arg three
-> (one -> two -> three -> Expression)
-> Function (Expression -> Expression -> Expression -> Expression)
fn4 :
String
-> Arg one
-> Arg two
-> Arg three
-> Arg four
-> (one -> two -> three -> four -> Expression)
-> Function
(Expression -> Expression -> Expression -> Expression -> Expression)
fn5 :
String
-> Arg one
-> Arg two
-> Arg three
-> Arg four
-> Arg five
-> (one -> two -> three -> four -> five -> Expression)
-> Function
( Expression
-> Expression
-> Expression
-> Expression
-> Expression
-> Expression
)
fn6 :
String
-> Arg one
-> Arg two
-> Arg three
-> Arg four
-> Arg five
-> Arg six
-> (one -> two -> three -> four -> five -> six -> Expression)
-> Function
( Expression
-> Expression
-> Expression
-> Expression
-> Expression
-> Expression
-> Expression
)
String
-> { a | declaration : Declaration }
-> { a | declaration : Declaration }

Add documentation to a function or value declared using this module.

String
-> res
-> { name : String
, builder : Fn res
, call : Expression -> List Expression -> Expression
}
Arg arg
-> { name : String
, builder : Fn (arg -> value)
, call : Expression -> List Expression -> e
}
-> { name : String
, builder : Fn value
, call : Expression -> List Expression -> Expression -> e
}
{ name : String
, builder : Fn Expression
, call : Expression -> List Expression -> res
}
-> Function res
(args -> Expression)
-> { name : String
, builder : Fn args
, call : Expression -> List Expression -> res
}
-> Function res
placeholder : Expression

You may want a placeholder function body if you're defining a function using Declare with the intention of calling the function instead of defining it.

In that case you can use placeholder!

Of note, if you generate the actual body of placeholder, it'll generate Debug.todo "Placeholder function body".

type alias Value =
{ value : Expression
, declaration : Declaration
, internal : Internal Expression
}
value : String -> Expression -> Value
String
-> List ( String, Maybe Annotation )
-> (List Expression -> Expression)
-> Function (List Expression -> Expression)

Virtual Modules

It's very common to have some modules that you both need to generate and use in your generated Elm code.

Here's an example, let's define a new module called MyModule:

import Elm
import Elm.Annotation
import Elm.Declare
import Elm.Op


type alias MyModule =
    { runCalculation : Elm.Expression -> Elm.Expression
    , myType : Elm.Annotation.Annotation
    }


myModule =
    Elm.Declare.module_ [ "MyModule" ] MyModule
        |> Elm.Declare.with
            (Elm.Declare.fn "runCalculation"
                (Elm.Arg.var "input")
                (\input -> Elm.Op.plus input (Elm.int 42))
            )
        |> Elm.Declare.with
            (Elm.Declare.customType "MyType"
                [ Elm.variant "One"
                , Elm.variant "Two"
                ]
            )

Once we've declared our module, we can do 2 things.

First, we can generate the Elm code for the module:

Elm.toFile myModule

Or we can include it in another module:

Elm.file [ "MyInternalFile" ]
    [ Elm.include myModule


    --... Whatever other declarations you want in this file.
    ]

Secondly, we can use the virtual module in our Elm code:

myModule.call.runCalculation (Elm.int 42)

Which will generate

MyModule.runCalculation 42

And handle the imports and everything.

type alias Module val =
{ name : List String
, declarations : List Declaration
, call : val
}
module_ : List String -> val -> Module val
{ a | declaration : Declaration, internal : Internal required }
-> Module (required -> val)
-> Module val
withUnexposed : { a | declaration : Declaration } -> Module val -> Module val
withDeclarations : List Declaration -> Module val -> Module val

Add multiple declarations to the module.

type alias Annotation =
{ annotation : Annotation
, declaration : Declaration
, internal : Internal Annotation
}
alias : String -> Annotation -> Annotation
customType : String -> List Variant -> Annotation
exposeConstructor : Annotation -> Annotation

Expose the constructors of a custom type.

toFile : Module val -> File
include : Module val -> Declaration

Include a module as a declaration in another module.

Note - Be aware that the module name of the included module must match the module name of the module you're including it in!

withSubmodule : Module submod -> Module (submod -> mod) -> Module mod

Add a module as a "submodule".

This can be useful for organizing particularly complex modules.

The only thing to be aware of here is that the module name for both of these modules must be the same or you're going to have a bad time.

Advanced Custom Types

In some cases you may want to generate a custom type and also have some helpers to

  1. Create an instance of that type
  2. Pattern match on that type

customTypeAdvanced is a helper function that will let you do that!

But it does get a little involved, so if you don't need it, trying something simpler may be the way to go.

As an example, here's how to create a helper for the Maybe type.

maybe =
    Elm.Declare.customTypeAdvanced "Maybe"
        { exposeConstructor = True }
        (\nothing just ->
            { nothing = nothing
            , just = just
            }
        )
        |> Elm.Declare.variant0 "Nothing" .nothing
        |> Elm.Declare.variant1 "Just" (Elm.Annotation.var "a") .just
        |> Elm.Declare.finishCustomType


-- Which then allows you to create an instance of the type via
maybe.make_.just (Elm.int 42)
-- Which would generate `Just 42`


-- And if you want to use it as a case expression, you can do this:


maybe.case_ (Elm.val "a")
    { just = \a -> a
    , nothing = Elm.string ""
    }


-- Which would generate
case a of
   Just a -> a
   Nothing -> ""
String
-> { exposeConstructor : Bool }
-> make_
-> CustomTypeBuilder case_ make_
type alias CustomType make_ =
{ declaration : Declaration
, annotation : Annotation
, make_ : make_
, case_ : Expression -> make_ -> Expression
, internal :
{ annotation : Annotation
, make_ : make_
, case_ : Expression -> make_ -> Expression
}
}
String
-> (case_ -> Expression)
-> CustomTypeBuilder case_ (Expression -> make_)
-> CustomTypeBuilder case_ make_
String
-> (case_ -> Expression -> Expression)
-> Annotation
-> CustomTypeBuilder case_ ((Expression -> Expression) -> make_)
-> CustomTypeBuilder case_ make_
String
-> (case_ -> Expression -> Expression -> Expression)
-> Annotation
-> Annotation
case_
((Expression -> Expression -> Expression) -> make_)
-> CustomTypeBuilder case_ make_
String
-> (case_ -> Expression -> Expression -> Expression -> Expression)
-> Annotation
-> Annotation
-> Annotation
case_
((Expression -> Expression -> Expression -> Expression) -> make_)
-> CustomTypeBuilder case_ make_
String
-> ( case_
-> Expression
-> Expression
-> Expression
-> Expression
-> Expression
)
-> Annotation
-> Annotation
-> Annotation
-> Annotation
case_
( (Expression -> Expression -> Expression -> Expression -> Expression)
-> make_
)
-> CustomTypeBuilder case_ make_
type CustomTypeBuilder case_ make_
String
-> List Annotation
-> (case_ -> branch)
-> (Arg branch -> Arg Expression)
-> ((List Expression -> Expression) -> previousMake_ -> make_)
-> CustomTypeBuilder case_ previousMake_
-> CustomTypeBuilder case_ make_
finishCustomType : CustomTypeBuilder make_ make_ -> CustomType make_

Advanced records

You can use these functions to declare a record type alias and to also get a constructor for it.

point =
    Elm.Declare.record "Point"
        |> Elm.Declare.withField "x" .x Elm.Annotation.float
        |> Elm.Declare.withField "y" .y Elm.Annotation.float
        |> Elm.Declare.buildRecord


p =
    point.make
        { x = Elm.float 0
        , y = Elm.float y
        }

This gives you compile time guarantees for the field names being correct.

record : String -> Record type_
String
-> (type_ -> Expression)
-> Annotation
-> Record type_
-> Record type_
Record type_
-> { annotation : Annotation
, declaration : Declaration
, internal : Internal Annotation
, make : type_ -> Expression
}
type Record type_

Internal things

type Internal val

You can safely ignore this type. It's used internally for some type bookkeeping, but you should never have it in your type signatures or anything.