mdgriffith/elm-codegen/4.2.2

NOTE: the latest version is 6.0.3

Elm

type alias File =
{ path : String
, contents : String
, warnings :
List
{ declaration : String
, warning : String
}
}
file : List String -> List Declaration -> File

Build a file!

Elm.file [ "My", "Module" ]
    [ Elm.declaration "placeholder"
        (Elm.string "a fancy string!")
    ]

Basics

type alias Expression =
Expression
toString : Expression -> String

See what code this expression would generate!

Note - Check out the Elm.ToString module if this doesn't quite meet your needs!

bool : Bool -> Expression
int : Int -> Expression
float : Float -> Expression
char : Char -> Expression
string : String -> Expression
hex : Int -> Expression
unit : Expression
maybe : Maybe Expression -> Expression
just : Expression -> Expression
nothing : Expression
list : List Expression -> Expression
tuple : Expression -> Expression -> Expression
triple : Expression -> Expression -> Expression -> Expression
withType : Annotation -> Expression -> Expression

Sometimes you may need to add a manual type annotation.

import Elm.Annotation as Type


Elm.value
    { importFrom = []
    , name = "myString"
    , annotation = Nothing
    }
    |> Elm.withType Type.string

Though be sure elm-codegen isn't already doing this automatically for you!

Records

record : List ( String, Expression ) -> Expression
Elm.record
    [ ( "name", Elm.string "Elm" )
    , ( "designation", Elm.string "Pretty fabulous" )
    ]
get : String -> Expression -> Expression
record
    |> Elm.get "field"

results in

record.field
updateRecord : List ( String, Expression ) -> Expression -> Expression
myRecord
    |> updateRecord
        [ ( "designation", Elm.string "Pretty fabulous" )
        ]

Results in

{ myRecord | designation = "Pretty fabulous" }

Flow control

ifThen : Expression -> Expression -> Expression -> Expression
ifThen (Elm.bool True)
    (Elm.string "yes")
    (Elm.string "no")

Will generate

if True then
    "yes"


else
    "no"

If you need more than one branch, then chain them together!

 Elm.ifThen (Elm.bool True)
    (Elm.string "yes")
    (Elm.ifThen (Elm.bool True)
        (Elm.string "maybe")
        (Elm.string "no")
    )

Will generate

if True then
    "yes"


else if True then
    "maybe"


else
    "no"

Note If you need let or case expressions, check out the docs for Elm.Let or Elm.Case!

Declarations

A Declaration is anything that is at the "top level" of your file, meaning all values with no indentation.

type alias Declaration =
Declaration
comment : String -> Declaration

Renders a multiline comment.

Elm.comment """Here is my comment!"""

Will generate

{- Here is my comment! -}
declaration : String -> Expression -> Declaration
withDocumentation : String -> Declaration -> Declaration

Add a documentation comment to a declaration!

expose : Declaration -> Declaration

By default, everything is exposed for your module.

However, you can tag specific declarations you want exposed, and then only those things will be exposed.

{ exposeConstructor : Bool
, group : Maybe String
}
-> Declaration
-> Declaration

You can also add a group tag to an exposed value. This will automatically group the docs statements in the module docs.

For precise control over what is rendered for the module comment, use fileWith.

List String
-> { docs :
List
{ group : Maybe String
, members : List String
}
-> List String
, aliases : List ( List String, String )
}
-> List Declaration
-> File

Same as file, but you have more control over how the module comment is generated!

Pass in a function that determines how to render a @docs comment.

Each exposed item is grouped based on the string used in exposeWith.

aliases allow you to specify a module alias to be used.

Elm.fileWith [ "MyModule" ]
    { docs = List.map Elm.docs
    , aliases =
        [ ( [ "Json", "Encode" ], "Encode" )
        ]
    }
    [-- whatever declarations you desire.
    ]

would make an import statement like

import Json.Encode as Encode

All values rendered in this file that are from this module would also automatically respect this alias as well.

{ group : Maybe String
, members : List String
}
-> String

Render a standard docstring.

@docs one, two, three

If a group has been given, it will be rendered as a second level header.

## Group name

@docs one, two, three

Functions

fn :
( String, Maybe Annotation )
-> (Expression -> Expression)
-> Expression

Create a function with a single argument.

This may seem a little weird the first time you encounter it, so let's break it down.

Here's what's happening for the fn* functions —

  • The String arguments are the names of the arguments for the generated function.
  • The attached Maybe Annotation is the type annotation. If you provide Nothing, then elm-codegen will infer the type for you!
  • The (Expression -> Expression) function is where we're providing you an Expression that represents an argument coming in to the generated function.

So, this

Elm.fn ( "firstInt", Nothing )
    (\firstArgument ->
        Elm.Op.plus
            (Elm.int 42)
            firstArgument
    )

Generates

\firstInt -> 42 + firstInt

If you want to generate a top level function instead of an anonymous function, use Elm.declaration.

Elm.declaration "add42" <|
    Elm.fn ( "firstInt", Nothing )
        (\firstArgument ->
            Elm.Op.plus
                (Elm.int 42)
                firstArgument
        )

Results in

add42 : Int -> Int
add42 firstInt =
    42 + firstInt

Note — Elm CodeGen will protect variable names if they're used in a nested fn* by adding a string of numbers to the end of the name. So, you may see a variable name be something like myVariable_0_1.

If you absolutely don't want this behavior, you'll need to use functionAdvanced.

fn2 :
( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> (Expression -> Expression -> Expression)
-> Expression
fn3 :
( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> (Expression -> Expression -> Expression -> Expression)
-> Expression
fn4 :
( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> (Expression -> Expression -> Expression -> Expression -> Expression)
-> Expression
fn5 :
( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( Expression
-> Expression
-> Expression
-> Expression
-> Expression
-> Expression
)
-> Expression
fn6 :
( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( String, Maybe Annotation )
-> ( Expression
-> Expression
-> Expression
-> Expression
-> Expression
-> Expression
-> Expression
)
-> Expression
List ( String, Maybe Annotation )
-> (List Expression -> Expression)
-> Expression

You may run into situations where you don't know the number of arguments for a function at compile-time.

In that case you can use function. It follows the same pattern as the fn* functions.

Provide it with —

  • A list of argument names and an optional type
  • A function which will be given all the input arguments as Expressions.
functionReduced : String -> (Expression -> Expression) -> Expression

This is a special case of function declaration which will reduce itself if possible.

Meaning, if this would generate the following code

\myArg -> someOtherFunction myArg

Then it will replace itself with just

someOtherFunction

Note you likely won't need this! It's generally used by the package-helper generator, but that might be a relatively special case.

Custom Types

customType : String -> List Variant -> Declaration

A custom type declaration.

Elm.customType "MyType"
    [ Elm.variant "One"
    , Elm.variantWith "Two"
        [ Elm.Annotation.list Elm.Annotation.string ]
    ]

Will result in

type MyType
    = One
    | Two (List String)
customTypeWith : String -> List String -> List Variant -> Declaration

A custom type declaration, with the ability to specify in which order to put the type variables.

Elm.customTypeWith "MyType"
    [ "addVar", "twoVar" ]
    [ Elm.variantWith "One"
        [ Elm.Annotation.var "oneVar" ]
    , Elm.variantWith "Two"
        [ Elm.Annotation.var "twoVar" ]
    ]

Will result in

type MyType addVar twoVar oneVar
    = One oneVar
    | Two twoVar

Notice how nonexisting variables (as used in phantom types) are included, and missing variable are automatically added.

type Variant
variant : String -> Variant
variantWith : String -> List Annotation -> Variant
alias : String -> Annotation -> Declaration

A type alias declaration.

import Elm.Annotation as Type


Elm.alias "MyAlias"
    (Type.record
        [ ( "one", Type.string )
        , ( "two", Type.int )
        , ( "three", Type.var "content" )
        ]
    )

Should result in

type alias MyAlias content =
    { one : String
    , two : Int
    , three : content
    }
aliasWith : String -> List String -> Annotation -> Declaration

A type alias declaration, with the ability to specify in which order to put the type variables.

import Elm.Annotation as Type


Elm.aliasWith "MyAlias" [ "twoVar", "nonexistingVar", "oneVar" ]
    (Type.record
        [ ( "one", Type.var "oneVar" )
        , ( "two", Type.var "twoVar" )
        , ( "three", Type.var "threeVar" )
        ]
    )

Should result in

type alias MyAlias twoVar oneVar threeVar =
    { one : oneVar
    , two : twoVar
    , three : threeVar
    }

Notice how nonexisting variables are omitted, and missing variable are automatically added.

Ports

portIncoming : String -> List Annotation -> Declaration
import Elm.Annotation as Type


Elm.portIncoming "receiveMessageFromTheWorld"
    [ Type.string
    , Type.int
    ]

Results in

port receiveMessageFromTheWorld :
    (String -> Int -> msg)
    -> Sub msg

Note You generally only need one incoming and one outgoing port!

If you want to vary the messages going in and out of your app, don't use a huge number of ports, instead write Json encoders and decoders.

This will give you more flexibility in the future and save you having to wire up a bunch of stuff.

Another note - You may need to expose your port explicitly using Elm.expose.

portOutgoing : String -> Annotation -> Declaration

Create a port that can send messages to the outside world!

import Elm.Annotation as Type


Elm.portOutgoing "tellTheWorld" Type.string

will generate

port tellTheWorld : String -> Cmd msg

Parsing existing Elm

parse : String -> Result String { declarations : List Declaration }
unsafe : String -> Declaration

This will insert the given string into your generated file.

Check out the using packages/helpers guide. If you're reaching for this, it's likely you'd be better off using a local helper file!

Low-level

apply : Expression -> List Expression -> Expression
val : String -> Expression
{ importFrom : List String
, name : String
, annotation : Maybe Annotation
}
-> Expression
unwrap : List String -> String -> Expression -> Expression

Unwraps a single-variant type

Elm.declaration "myFunction" <|
    Elm.fn "val"
        (\val ->
            Elm.unwrap "MyType" val
        )

Results in the following lambda

myFunction val =
    (\(MyType val) -> val) val
unwrapper : List String -> String -> Expression

Generate a lambda which unwraps a single-variant type.

Elm.unwrapper [ "MyModule" ] "MyType"

Results in the following lambda

\(MyModule.MyType val) -> val