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 Android1 and iOS2, with a thin platform-native UI layer3. 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,4 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!5 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.