Hatter is like flutter but instead of dart, haskell!
Write native mobile apps in Haskell! This works similar to react native where we have tight bindings on the existing UI (user interface) frameworks provided by android and iOS.
This project cross-compiles a Haskell library to Android (APK) and iOS (static library / IPA), with a thin platform-native UI layer (Kotlin for Android, Swift for iOS). There is support for android wear and wearOS as well, because I personally want to build apps for those. iOS and Android support was just a side effect.
Hatter fully controls the UI. This is different from say Simplex chat where Java or Swift calls into their Haskell library. Hatter wrote all Swift and Java code youâll ever need, so you can focus on your sweet Haskell.
Haskell is a fantastic language for UI. Having strong type safety around callbacks and widgets makes it a lot easier to write them. I basically copied Flutterâs approach to encode UI,1 but in flutter itâs a fair bit of guess work, what to write where. Itâs very nice in Haskell however. Iâm annoyed at the languages they keep shoving into my face for UI. With vibes I put my malice into crafting something good. Flutter UI DSL (Domain Specific Language) is already pretty good, but the syntax is complex, and it inherited many foot guns from Java. Furthermore itâs annoying to use android studio, when there is a perfectly good emacs. With Hatter I can keep e-maxxing!
Example app:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.IORef (newIORef, readIORef, modifyIORef')
import Data.Text qualified as Text
import Foreign.Ptr (Ptr)
import Hatter
( startMobileApp, MobileApp(..), AppContext
, loggingMobileContext
, newActionState, runActionM, createAction, Action
)
import Hatter.Widget
main :: IO (Ptr AppContext)
main = do
actionState <- newActionState
counter <- newIORef (0 :: Int)
increment <- runActionM actionState $
createAction (modifyIORef' counter (+ 1))
startMobileApp MobileApp
{ maContext = loggingMobileContext
, maView = \_userState -> do
n <- readIORef counter
pure $ Column
[ text $ "Count: " <> Text.pack (show n)
, button "+" increment
]
, maActionState = actionState
}I had a lot of trouble figuring out initialization. What we want is to let the UI framework call into Haskell from Java or Swift. But we also donât want to force the user to write their own FFI layer. Thatâs what the library supposed to do. Finding out that main can return whatever type it wants was a great solution. AppContext now returns all callbacks we need for a functioning app. Which includes life cycle management. The Ptr is a stable pointer, which allows us to avoid globals completely!2 Pretty neat! And no, AI will not find solutions like this for you. Youâve to torture it in just the right way ;).
Weâre All Mad Here: Wrangling Probabilities
The biggest âinnovationâ here is to just force it to write integration tests for every feature. We want it to self validate via code for practically everything. CI is its code reviewer. This prevents it from regressing without you noticing it. At times itâll still try disabling the test suite, or pretend the errors were already existing. This usually means it got a too âdifficultâ job, and youâve to break it down in smaller steps. Or ask it to just do research on one part. Doing âresearchâ isnât a standard skill like planning mode, but itâs very useful because it allows it to be more creative. The problem is that it tries placating you and if it thinks it canât do a programming task in reasonable time, itâll start cheating. If you tell it to research you sorta let off the time pressure.
Most designs and architectures it comes up with are kinda bad. Youâve to help it a lot here, the entire animation system wouldâve been so bad if I wouldâve let it have itâs way. Firstly it did tree diffing very wrong, and secondly it started out with wanting the user to register itâs own handlers or something? Now an animation is just a node in the widget tree. So it becomes easy to use, I guess I value that a lot, which isnât a right or wrong answer per se, like a Nix build.
Itâs good at solving Nix builds, but it still does weird things. it wanted to replace the generic Nix builder with its own scribbles for dependencies, which would break the entire hpkgs dsl. This would for example mean youâd jailbreak a library and then hatter would ignore your instructions. I had it to tell it to please not do that. It claimed this wasnât possible. So I asked it to research it anyway, And it turns out it was easy to use the standard builders. It just needed some research time I guess. It implements stuff for the task at hand and there is no foresight at all. Thatâs okay though, itâs fast. Fast is good.
Another big problem is template Haskell. Weâre doing cross compiling and thatâs very troublesome, because weâve to execute the cross compiled code, however this is now the wrong architecture! This was however mostly solved by the AI. Itâll happily grind away for 3 hours on a Nix build to get it to work, if you tell it that is its entire job. This kinda kept coming back in various ways and Iâm not sure why. Partly because I donât understand much of the Nix harness it built I guess. Itâs so weird getting functioning software you donât fully understand. I did have it make some reports on it, which I donât understand. It says I should upstream, but I still donât understand. Iâll do nothing for now.
I made prrrrrrr in the hatter framework. Iâm intending to make at least a couple more apps in that, Iâve ideas but I never had a nice framework to work on in. Around 50k lines of code spent in 2 weeks or so - Pretty crazy. Worth it to reach contentment. Haters gonna hate, hatters gonna hat.