Functions for working with Elm source code.
The compiler phases in general look like this:

Useful eg. for tools like elm-format that convert back to the Elm
representation, trying to do as few changes as possible. Ie. it would be bad
if elm-format changed
\a b c -> a + b + cto
\a -> \b -> \c -> a + b + cThat transformation is one of the things the Desugar phase does. So tools like
elm-format probably don't want to touch that phase, and will only want to parse!
Parse a module (one *.elm file). Get a Module datastructure back, holding
the information about its exposed values, imports, declarations and more.
A file like
module Main exposing (foo)
import Bar as B exposing (bar)
foo =
123will get parsed into
{ imports =
Dict.fromList
[ ( "Bar"
, { moduleName = "Bar"
, as_ = Just "B"
, exposing_ = Just (ExposingSome [ ExposedValue "bar" ])
}
)
]
, name = "Foo"
, filePath = "src/Foo.elm" -- what you pass into the function
, declarations =
Dict.fromList
[ ( "foo"
, { module_ = "Foo"
, name = "foo"
, body = Value (AST.Frontend.Int 123)
}
)
]
, type_ = PlainModule
, exposing_ = ExposingSome [ ExposedValue "foo" ]
}Parse multiple modules (*.elm files) - see parseModule for details.
Parse a single import statement, like
import Foo as F exposing
( foo
, Bar(..)
, Baz
)into
{ moduleName = "Foo"
, as_ = Just "F"
, exposing_ =
Just
(ExposingSome
[ ExposedValue "foo"
, ExposedTypeAndAllConstructors "Bar"
, ExposedType "Baz"
]
)
}Parse a single declaration, like
foo =
123into
{ module_ = "Foo" -- what you pass into the function
, name = "foo"
, body = Value (AST.Frontend.Int 123)
}After we parse the source code from a String to the AST, we desugar it -
simplify the AST type as much as possible to make later phases simpler and easier.
The best example to illustrate this (it doesn't actually happen though!) is
let vs where. Imagine if Elm allowed for where constructs in its syntax,
like Haskell does:
foo = x + y
where
x = 123
y = x + 2Then we'd like to convert these to let constructs (or the other way round)
as soon as possible, so that the other phases don't need to handle two
almost identical scenarios all over the place.
Examples of real desugarings include:
Desugar a single expression like
Lambda
{ arguments = [ "x", "y" ]
, body = AST.Frontend.Int 42
}into AST like
Lambda
{ argument = "x"
, body =
Lambda
{ argument = "y"
, body = AST.Frontend.Int 42
}
}Desugar a module (one *.elm file).
Desugar multiple modules (*.elm files) - see desugarModule for details.
Desugar a module (one *.elm file), without the intention of desugaring
another one.
These functions compute the types of the given expressions, as well as check them against the user-defined type annotations.
Note that the more of your code you'll give these functions at once, the better
the type inference will be. So it's advisable to eg. run inferModules
once instead of running inferModule on each of your modules.
Infer the types of expressions in a module (a single *.elm file).
Infer the types of expressions in multiple modules (*.elm files).
After typechecking the expressions are ready to be optimized. (The inferred types are available to you inside the optimizations! (Unfortunately, the location information is also available to you inside the optimization. Sorry.))
The default optimizations the elm-in-elm compiler uses.
Try evaluating it in the REPL: you should see that each optimization function
has a name String next to it.
> import Elm.Compiler
> Elm.Compiler.optimizations
[ ("plus", ...)
, ("cons", ...)
, ("if-literal-bool", ...)
]
>
You can use this to filter optimizations you don't want!
wantedOptimizations = Set.fromList [ "plus", "if-literal-bool ]
optimizations
|> List.filter (\(name, _) -> Set.member name wantedOptimizations)The optimizations are of the shape Expr -> Maybe Expr, and you shouldn't
recurse inside them. That's done for you automatically. You should only
consider one "layer", whatever that means for your optimization.
Eg. the plus optimization is (conceptually):
optimizePlus : Expr -> Maybe Expr
optimizePlus expr =
case expr of
Plus (Int a) (Int b) ->
Just (Int (a + b))
_ ->
NothingWhere it only is concerned with the addition of two integers. Any other expression will just get ignored.
Your optimizations are then run from the bottom up, repeated as many times as
needed on the same expression until the optimization produces Nothing
("Nothing else I can do here!"). At that point, the engine moves "up" to the
parent expression, and so on until it reaches and optimizes the topmost
expression.
Note the optimizations in the list of optimizations are combined in such a way that they take turns on the expression.
Optimize a given (typed) expression using the default set of optimizations.
For using your own optimizations instead of or in addition to the default ones,
look at the optimizeExprWith function.
Optimize a given (typed) expression using a custom set of optimizations.
Optimize all expressions in a given module using the default set of optimizations.
Note there is currently no inter-definition optimizations (inlining etc.) - only the optimizations on each separate expression.
For using your own optimizations instead of or in addition to the default ones,
look at the optimizeModuleWith function.
Optimize all expressions in a given module using a custom set of optimizations.
Note there is currently no inter-definition optimizations (inlining etc.) - only the optimizations on each separate expression.
Optimize all expressions in multiple modules using the default set of optimizations.
For using your own optimizations instead of or in addition to the default ones,
look at the optimizeModulesWith function.
Optimize all expressions in multiple modules using a custom set of optimizations.
If you want to typecheck the code but then don't do anything with the types afterwards, you can drop them from the expressions you have. This is essentially a move backwards in the compiler phases:

Drop types from a single expression.
We're hitting limitations of the Elm Packages website, and the type shown isn't very descriptive. The real type of this function is:
Typed.LocatedExpr -> Canonical.LocatedExprExample usage:
Located
{ start = ..., end = ... }
( Tuple
(Located ... ( Int 12, Type.Int ))
(Located ... ( String "Hello", Type.String ))
, Type.Tuple Type.Int Type.String
)becomes
Located
{ start = ..., end = ... }
(Tuple
(Located ... (Int 12)
(Located ... (String "Hello")
)If location info is not useful to you either, look for the unwrap functions
in the various Elm.AST.* modules.
Drop types from all expressions in the module.
We're hitting limitations of the Elm Packages website, and the type shown isn't very descriptive. The real type of this function is:
Module Typed.LocatedExpr
-> Module Canonical.LocatedExprDrop types from all expressions in all the modules.
We're hitting limitations of the Elm Packages website, and the type shown isn't very descriptive. The real type of this function is:
Dict ModuleName (Module Typed.LocatedExpr)
-> Dict ModuleName (Module Canonical.LocatedExpr)
Parse a single expression like
into AST like
If you don't need the location information and want to only keep the expressions, use
Elm.AST.Frontend.unwrapto get something like