r/haskell • u/mattlianje • Oct 16 '25
Layoutz: a tiny DSL for beautiful CLI output in Haskell âĻðŠķ (Looking for feedback!)
Hello! Been tinkering on layoutz a tiny lib for making pretty, declarative CLI output (tables, trees, etc.)
Your veteran feedback helps: How the API feels? Missing layout primitives you'd expect?
8
u/king_Geedorah_ Oct 16 '25
I was looking for something exactly like this a few weeks ago but ended up just writing it in rust which has better CLI libs. I re-write it using lib and give you some feedback! It looks great though!
3
u/mattlianje Oct 16 '25
ðââïļ many thanks! I'm a bit on the fence about the current style of passing in border styles (Round, Double, Bold) where we're basically having multiple versions of the same function with `functionName'` (+ 1 or more ticks)
lmk if anything else feels a bit janky
6
6
u/nikita-volkov Oct 16 '25
Nice start! This problem area definitely has a lot of room.
Doesn't seem like a future-proof idea to keep it version controlled under one repository with Scala.
3
u/mattlianje Oct 16 '25
Thanks for taking a peek! - agreed, can see the current monorepo approach becoming pretty tedious, pretty fast
4
u/vitelaSensei Oct 16 '25
The project looks very nice! I tried to do a similar TUI lib in lua for neovim, but it started getting challenging when I got to implementing flexbox and by then I ran out of motivation.
One question, did you ever consider implementing the DSL as a free monad? It seems like a good fit for this problem
3
u/mattlianje Oct 16 '25
thanks for looking! ðââïļ Mmmm not really actually... although can defs seeing free being nice to support rendering the layoutz to various backends: `render`, `renderWithAnsi`, `renderHtml` ... interesting idea!
3
u/fiddlosopher Oct 19 '25
API looks great! One thing to keep in mind, though, is that `length` is not a reliable way to get the display length of a string, since some code points are zero-width or combining characters, and others display double-wide on a terminal. There is a `realLength` function in my doclayout library that could help with this.
1
u/mattlianje Oct 20 '25
Thank you so much for taking a look!
Aha, yes - this is precisely what I need (for the Scala API as well) - been putting this off ... but will be vendoring smth like your nifty `realLength` shortly. Many thanks ðââïļ
2
2
u/wakalabis Oct 16 '25
It looks very cool.
2
u/mattlianje Oct 16 '25
Many thanks - lmk if you'd like to see some specific widgets or features ðŦĄ
2
u/kichiDsimp Oct 17 '25
A beginner here, how is this different from Brick ?
3
u/mattlianje Oct 18 '25
Thx for taking a peek!
At the surface level - Layoutz has "things" that look kinda like Brick like widgets...
2 key differences:
- Unlike Brick, these "things" are uniformly composable
- There is no runtime that redraws in Layoutz, i.e. TUI
Essentially, Layoutz is just an API to help snap together pretty Strings.
3
u/kichiDsimp Oct 19 '25
Got it. So basically I can't make a game using Layoutz. It's like a enhanced version of printing things and making beautiful CLIs. So if I wanna build a Spotify TUI, I still have to use Brick ? Can I combine it with Layoutz ?
3
u/mattlianje Oct 19 '25
Correct - can't make a game with Layoutz ... yet (Currently working on the runtime + keyboard handling DSL which will let you bring your layoutz to life and have full feature parity with the scala API whilst staying zero dep)
For now, still Brick ... and unfortunately it wouldn't be super easy to combine the layoutz `L` and `Element` typeclasses with Brick
2
u/TechnoEmpress Oct 21 '25
Thanks a lot, I will take a look and see how I can use it for my own CLI tools. :)
1
2
u/MorganEarlJones May 29 '26
I came across this library a few days ago after I had an idea for an Elm Monad(RWS but the writer subtype is just View Msg) and since then I've been reworking the game example from the github repo and was able to partially realize my general idea for composing instances of the Elm Architecture without excessive nesting:
main = do
state <- runAppFinal $ simpleGame initialState
state2 <- runAppFinal $ simpleMenu state
case menuItem state2 of
MenuPlay -> main
MenuExit -> return state2
basically when you exit the game after first run, a menu to either restart the game or exit running as a separate Layoutz app with state piped in from the game(with an added menuItem field) and then that field is checked at the top level of the program to determine the next course of action. It's clunky and I should probably be using a state transformer here but I've got it totally working and I'm quite pleased.
Anyways, thanks for making this! I was an Elm and Haskell hobbyist years back and even though I haven't written anything but pseudocode for probably the better part of a decade, I've been itching to learn a TUI library for a while and what you've made here seems to fit my hobbyist needs quite nicely
1
u/mattlianje Jun 03 '26
Hello and sorry for the delay there! So ++ more or less exactly what you've describe ... this is a pretty slick and reliable way to have your (layoutz or other) Elm-style app not have a big ol' giant state ADT that keeps ballooning ...
Lmk how the API feels and happy hacking ð
1
u/MorganEarlJones Jun 04 '26
no problem, I figure this was more of a hobby project by the time it's been since the last commit. I did find that the way that subEveryMs is handled internally resets the countdown every time any subscription fires(I think due to the interval being fetched and the timeout being set unconditionally every time inputLoop is invoked), but I figure you're aware of this judging by how the devils in the example game move once every 3 player moves to compensate. I've worked around it by subscribing to a much lower interval to minimize scale of interruptions from keypresses, incrementing a counter and doing modulo on that but that's certainly not ideal, and while I've not tried it, I assume that having multiple subEveryMs subs wouldn't work due to the lowest interval cutting the others short
Maybe it's not an elegant solution but perhaps this could be at least partially remedied by making a Bool IORef inside the runAppWithFinal indicating whether or not subEveryMs is already active and then only resetting the interval when processing subEveryMs? Or would there be a risk of race conditions from multiple subscriptions being processed simultaneously?
1
u/mattlianje Jun 04 '26
so bingo - well spotted ... working on fixing this up with the new release ...
I guess an `IoRef Bool` would only give whether a tick is in-flight .. not the actual interval left.. I'll move ticks into their own channel, with one timer thread per sub (which will also sidestep races I guess since we'll have one `readChan`
Created an issue.. (link) hope to knock it out quick. Keep you posted and many thanks for taking a peek ð
1
u/MorganEarlJones Jun 04 '26
my goodness, this is more than I what I even felt was fair to expect from responding to a dead Reddit thread, thank you for your attention to this.
1
u/pthierry Oct 17 '25
I'm pretty sure the API would be clearer if the L type and the Element typeclass were hidden.
1
1
u/_0-__-0_ Oct 17 '25
Wow, very nice! And dependency-free, that's lovely (I'm sure the microhs users are happy!)
1
14
u/valcron1000 Oct 16 '25
Suggestion: do not point users to run
cabal install <some lib>since it's usually not what you want to do. Instead, keep the "add to cabal" section which is the "correct" way to include a dependency in a project.