Skip to content

Instantly share code, notes, and snippets.

@rupertlssmith
Last active May 28, 2025 12:28
Show Gist options
  • Save rupertlssmith/9405d69acdf561a8b553450f7c5651db to your computer and use it in GitHub Desktop.
Save rupertlssmith/9405d69acdf561a8b553450f7c5651db to your computer and use it in GitHub Desktop.
Lamdera Talk

Resources

  1. https://github.com/lamdera/compiler
  2. https://github.com/rupertlssmith/lamdera-compiler/tree/exploration
  3. This Gist - https://gist.github.com/rupertlssmith/9405d69acdf561a8b553450f7c5651db

Introductions/Motiviations

  • Undergrad final year project. Prolog compiler to bytecode, LLVM.
  • I want to learn more about the compiler code, intrigued about approach taken to extend by Lamdera.
  • Get more people involved.
  • No updates to Elm compiler since... Can we just use Lamdera in production instead?
  • New backends, Elm to good to just be used for UI work, really strong affinity to domain modelling. Elms type system, and package system with semantic versioning, should be at the heart of enterprise systems, not just the edges.

First Half - Build Instructions

  • Walk through of setup and build of Lamdera.
  • Building standalone distribution.
  • IDE setup.

Do people want to follow along on their own machines and work through the setup and build together? What platforms do we have? Mac, Linux, Windows...

Second Half - Code Exploration

  • Haskell vs Elm to help us reading Haskell.
  • Diff Lamdera and Elm.
  • Compiler model and code tour.
  • Can Lamdera run as a drop-in replacement for Elm?

Ideas

What compiler ideas do you have and would they fit into Lamdera?

  • AI code chunking.
  • Recent Language Server announced on Slack.
  • New compiler targets - JVM, LLVM, WASM, ...
  • Parser with full error recovery.

Build it with Cabal:

Mario advises against building with Cabal as managing dependencies and upgrades to them is a pain. But for completeness lets try it.

These build steps were extracted from the distribution/docker/x86_64-musl.dockerfile. There are already scripts under distribution/ folder to build for many different platforms. This is probably the easiest way of doing it. But here is a more epxlicit step by step way for a Linux box.

Not that compared with the Dockerfile I had to remove static linking, it has CABALOPTS="-f-export-dynamic -fembed_data_files --enable-executable-static -j4". Making a more portable statically linked binary is a bit tricky with Haskell, and that is why the Dockerfile builds on Alpine Linux to do that since it uses musl libc which is a more standalone libc.

curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
export PATH="${PATH}:~/.ghcup/bin"

ghcup install ghc 9.2.8 --set
ghcup install cabal 3.10.1.0 --set    
cabal update

export CABALOPTS="-f-export-dynamic -fembed_data_files -j4"
export GHCOPTS="-j4 +RTS -A256m -RTS -split-sections -optc-Os -optl=-pthread"

cabal build $CABALOPTS --ghc-options="$GHCOPTS" --only-dependencies
cabal build $CABALOPTS --ghc-options="$GHCOPTS"

Build it with Stack:

Haskell has build systems called Cabal and Stack. Stack is more modern and builds on top of Cabal and is the recommended way to build Lamdera. The original Elm compiler only has a Cabal build.

On Debian I was able to install Stack through the package manager:

sudo apt-get install haskell-stack
sudo stack upgrade

Alternatively there are install scripts:

curl -sSL https://get.haskellstack.org/ | sh

One thing I noticed is that when running stack ghci it downloaded ghc-tinfo6-9.8.4, which must be the ghc compiler itself, but what is the tinfo6 bit? Stack overflow answer: "It is a GHC build variant that links to libtinfo.so.6 (as opposed to linking to some version of libncurses)". So seems like Stack itself knows which compiler version it needs and takes care of fetching it.

Creating a compiler binary is as simple as:

stack install

If you just want to build it, then run it without installing:

stack build
stack exec lamdera

Get the code and compare with the original:

Get the Lamdera code, and required git submodules.

mkdir -p lamdera/lamdera
git clone https://github.com/lamdera/compiler.git lamdera/lamdera/

cd lamdera/lamdera
git submodule init && git submodule update

Get the Elm compiler code (because it will be informative to compare):

mkdir -p elm/compiler
git clone https://github.com/elm/compiler.git elm/compiler/

Compare the codebases:

kdiff3 lamdera/lamdera elm/compiler

Find alternative implementations:

find builder/ compiler/ -name '*.hs' | xargs grep 'alternativeImplementation'
Elm Haskel
>> Control.Arrow.>>>
<< . Hmmm...
|> &
<| $
: ::
:: : Aaaghh!
type ... data ...
type alias ... type ...
Debug.log label value trace label value Elm prints label and value, Haskell prints just label. Both take on value for the whole expression.

Elm:

length : List a -> Int
length l =
   case l of
      [] -> 0
      x :: xs -> 1 + length xs

Haskell:

length :: [a] -> Int
length [] = 0
length (x:xs) = 1 + length xs 

Elm:

filter : (a -> Bool) -> List a -> List a
filter isGood list =
  foldr (\x xs -> if isGood x then cons x xs else xs) [] list

Haskell:

filter :: (a -> Bool) -> [a] -> [a]
filter pred [] = []
filter pred (x:xs)
 | pred x = x : filter pred xs
 | otherwise = filter pred xs

Elm:

 map5 :
    (a -> b -> c -> d -> e -> result)
    -> Task x a
    -> Task x b
    -> Task x c
    -> Task x d
    -> Task x e
    -> Task x result
map5 func taskA taskB taskC taskD taskE =
    taskA
        |> andThen
            (\a ->
                taskB
                    |> andThen
                        (\b ->
                            taskC
                                |> andThen
                                    (\c ->
                                        taskD
                                            |> andThen
                                                (\d ->
                                                    taskE
                                                        |> andThen (\e -> succeed (func a b c d e))
                                                )
                                    )
                        )
            )

Haskell:

map5 ::
  (a -> b -> c -> d -> e -> result) ->
  Task x a ->
  Task x b ->
  Task x c ->
  Task x d ->
  Task x e ->
  Task x result
map5 func taskA taskB taskC taskD taskE = do
  a <- taskA
  b <- taskB
  c <- taskC
  d <- taskD
  e <- taskE
  pure $ func a b c d e

Working with Hasell in IDE. ghcup, hls, Visual Studio

Install GHCup, and installer program for ghc, cabal and hls that will manage versions also:

https://www.haskell.org/ghcup/

Visual Studio plugin for Haskell:

https://marketplace.visualstudio.com/items?itemName=haskell.haskell#setup    

Installed the VS plugin via View -> Extensions -> Haskell. Once this was installed it prompted me to install hls via ghcup and did so automatically.

It seems to work better if you run vscode from the folder where you checked out Lamdera:

cd projects/lamdera-compiler
code .

Now file navigation seems to find things better.

I don't know why but vscode did something weird with formatting Haskell on save. Press cmd + shift + p then search for "save without formatting" and click on the configure icon, then bind it with 'cmd + s'.

Can Lamdera run as a drop-in replacement for Elm?

I turned the standard "Counters" Elm program into a Lamdera program. I gave it entry points main for standard Elm, and app for Lamdera.

Trying to build it as an Elm program yields something that does not run as a standalone Elm web app:

cd test-program
lamdera make src/Frontend.elm
python3 -m http.server 8000

But it will run under Lamdera live:

lamdera live

Can I use Lamdera as a drop-in replacement to build a standard Elm program? Its certainly not far off being able to do so, unsure if I just need the right config or would require a little bit of code modification to achieve this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment