Haskell

building

You do not install wasm32-wasi-ghc (reactor mode) - the docker image has it, and the current null0 bindings, baked in. There is nothing in your project to keep in sync with the engine.

docker run --rm --user $(id -u):$(id -g) \
  -v ./cart:/src -v ./webroot:/out \
  ghcr.io/notnullgames/null0-cart-haskell:latest mygame

The template wraps that in npm start, which also serves the result and rebuilds when you save.

Haskell notes

  • Everything is in IO. Each null0 call is imported through a generated C trampoline.
  • Carts get no environment variables, so anything reading $PWD sees nothing.

a cart, in Haskell

This is carts/haskell/simple/Main.hs in the engine repo - the same file CI builds into the cart below, so it always compiles.

{-# LANGUAGE ForeignFunctionInterface #-}

module Main where

import Null0

-- wasm32-wasi-ghc builds a reactor module, so main is never the entry point
main :: IO ()
main = return ()

foreign export ccall "load" load :: IO ()

load :: IO ()
load = do
  clear blue
  drawCircle 100 100 50 red
  drawText fontDefault "hello from Haskell" 170 92 white

-- callbacks (optional - add a `foreign export ccall` for each one you want)
--
--   foreign export ccall "update" update :: IO ()
--   update :: IO ()
--   update = return ()
carts/haskell/simple/Main.hs

callbacks

A cart implements the callbacks it cares about. In Haskell they look like this:

foreign export ccall update :: IO ()

See anatomy of a cart for the full list and what each one is passed.