Lil expressions flow right-to-left, like most members of the APL family. A minor quality-of-life feature I’ve adopted in the various Lil REPLs is the convention of binding the last expression of each request’s result to the name _, which allows you to make “forward progress” interactively without backtracking to define intermediate variables every time. If you realize you’ll need something again, you can name it after evaluating the expression which produced it:
I continue to be totally enamored by concatenative/tacit/stack-oriented/chained/pipelined/postfix/RPN/whatever-you-want-to-call it design, in particular its manifestation in Factor.
The given Uiua example (mercifully given using words rather than the symbols):
[3 4 5 10 23]
divide length on /+
For all the talk about “forward” it’s uncomfortable to me how the Uiua evaluation within a line happens backward.
An equivalent in Factor, where keep is close to on:
{ 3 4 5 10 23 }
[ sum ] keep length /
But this pattern of doing two things in sequence to the same item is common enough that bi is handy:
my_data.some_attr<enter>
<up arrow><type )><ctrl-a><type json.loads(><enter without going to the end>
json.loads(my_data.some_attr)
that’s one keystroke/key combo more.
Yes, it’s the basic example, but I find myself going to the middle of a pipe’d pipeline in my shell just as often, that’s what alt-left/ctrl-left or whatever are for. If I use vim bindings in an editor it’s also easier.
I may be completely missing the point, but my take is that for this to really matter it needed to be like 90% forward, where in practice it’s maybe 60% and so the perceived benefit is only “a little less going back”.
And if I sound like having a strong opinion, it’s because I have a mac as a work laptop for the first time and Home/End/ctrl-left/alt-left and so on behave differently than on Linux+Windows and I’m constantly failing to move, to a degree that I only notice now how I don’t notice it usually.
Yet, at the same time, pipes do feel much more satisfying to write to me than wrapping stuff in function calls. I feel like it has a lot to do with not just how you type the expression, but also how it looks in the end.
When you’re composing a pipe, you can see the pipe growing to the right. The left side remains as it was before. Wrapping something in a function call, on the other hand, means shifting the original expression to the right.
I feel like that incremental growth that happens with pipes is very satisfying.
Also half of the occurrences of people shouting “useless use of cat” are not grasping that the person who wrote it probably was just working incrementally from left to right ;)
So yeah, I’m not disagreeing per se - but I don’t really find it a meaningful difference.
Perhaps this isn’t the most motivating example, yeah.
I think that there’s a universe where, in the REPL, typing | right after evaluating an expression would wrap the previous expression in parentheses, put the cursor to the very left, and let you just type the function call.
where in practice it’s maybe 60%
So I see this as more an issue with the status quo in many languages. I’m constantly finding myself wishing I could just continue transforming, and Uiua is the only language that keeps up. That and shell scripting for the most part. like 85% of my thinking is in this mode, but I can’t reflect that in my code.
I do agree about the value of dancing around with Emacs bindings, one of the big reasons for me to stay on a Mac.
In general, these days everyone has access to a powerful structured editor, not only lispers. It’s just that:
few people know how to use it effectively
to be useful, it needs to be really polished, and I think only JB has institutional capacity for such polish (though maybe newer editors like helix, which put tree sitter front and center, are also good).
Factor has been on my radar for a while! I’m having fun messing around with Uiua that Factor is a bit by the wayside.
To be honest what I think I really need is embeddable versions of these languages. So when I’m working in a Python REPL, I can pipe my data into the languages and work off of that for a while, and then escape back out to the real world in the end.
I’m not that person, but I also gave it a try today, and found it pretty great that it’s nice and usable with only a few config options set (font, theme, disable gtk titlebar). Great work, and thanks for it!
The two immediate things I noticed keeping me off it for now: no sixel support, and some key combinations like shift+{home,end,pgup,pgdown} and ctrl+shift+{left,right} don’t seem to register.
Ah, the problem was that those keys are already bound to something by default, so keybind = clear fixes that problem! If it gets sixel support then I might try it full time.
me another person who isn’t the one you replying to, but if you’re open to information from non-switchers, here’s my story…
I’ve never actually tried your program (once I write my own, there’s zero realistic chance of me switching to something else anyway, so I look at other things just to see if there’s any ideas i want to steal), but I have some ideas you might want to consider too.
Bottom line up front:
Consider making all your gui events forwardable to terminal escape sequences, including tab control requests. So you can do some kind of terminal command for migrating sessions across displays. This means consider you have three tabs in a session on your Mac. SSH in from your Linux box and attach that session. Make it possible for the tabs display natively again from the linux instance.
Forward keys, or at least the commands those keys represent, through those remote sessions too, and write a client api helper lib. I think you support this already, but ensure it can work even for hotkeys in some situations, so it can forward the command over a ssh link too.
The long story:
For the last …. golly, over 11 years now, how time flies, I have used my own terminal emulator suite (in the D programming language, like virtually everything else i do lol). There’s three primary components: the terminal emulator core library, which is basically a big class with abstract methods the displays must implement, and a bunch of methods the display calls.
The next piece is the main gui implementation, so it does like override void displayScreen(....) { drawText(yada yada yada); } and then window.onkeyevent = function(event) { emulator.sendKeyInput(event.key); } and so on.
Looks like ghostty does basically this same thing, so you know those parts I’m sure.
The final piece is the detachable session implementation (so a gnu screen/tmux replacement), which forks into two parts: one is the terminal emulator core in memory, with its methods implemented like displayScreen() { nextTerminal.write(our contents); } and nextTerminal.oninput = function() { ourCore.sendRawInput(...); }, and the attach command, which manages what nextTerminal actually is. Making sure all my desired features could be reliably implemented and forwarded through this detached session drove a lot of the library api design.
I also have a terminal client library - think ncurses replacement - that I wrote before the emulator, but since expanded to include support for my custom features, so when I make a program for myself, I can opt into whatever progressive enhancement I want. For example, its getline function works just fine on xterm. But if running on my terminal emulator, it enables its special mode for application mouse event reporting on the line with the cursor (and only on the line with the cursor). So you can click around there if you want, without breaking selecting text elsewhere. Just nice to provide the ez options at all levels (for people who use my lib). The detachable session thing uses this - it is a bridge of sorts between the client and server, a proxy really. … like literally a proxy, that’s the term I should use lol.
So for a concrete example, I open up a terminal here, attach some_session, and middle click in it. The middle click event goes to the attach client, which forwards it to the some_session terminal emulator (which is an in-memory instance of the library object). That object sees a middle click event, and does the normal branch: if in mouse reporting mode, send the event through the pty to the application, or if not in mouse reporting mode, it calls the virtual pasteRequested method.
The pasteRequested implementation for the session backend does not immediately try to get the clipboard contents off the OS api! The backend doesn’t know where it is attached yet, so it actually sends that request up one more level via one of the xterm escape sequences (pty.write("\033]52;p;?\007"); though my code comment says “not quite compatible with xterm but kinda since xterm tends not to answer anyway” soooo maybe do it a bit differently, idk, works for me though). So now the attach client forwards this to the emulator it is attached to, which understands that escape sequence as “please send the clipboard contents”.
If it is another nested emulator, it does that again, sending the escape sequence up one more level, until it gets to the actual gui window, which finally fetches from the clipboard and sends it back down the chain. The attach client frontend is a terminal program that activates the alternate screen, bracketed paste, and such, so it now sees the bracketed paste content and is able to send that down the chain to its emulator core as a paste event, which can forward to its application, which may or may not have requested bracketed paste.
Each level unwraps and rewraps the escape sequences to higher level event objects and vise versa. Forwarding the escape sequences directly sometimes works, but sometimes breaks since there might be some need for graceful degradation, I prefer to make them all methods in the api so it can make that decision at the appropriate level. This means all my cool extension features work well even when migration sessions between desktop and laptop - no more loss through the terminal multiplexing proxy, so long as it is my code on both ends (and if it is back in a regular xterm or putty, it doesn’t work as well, but it does still work, so im never locked out either).
Previously, I was primarily an xterm+screen user, and switched to my own stuff in 2013. The two main reasons why I wrote my own terminal emulator was that yellow on white or blue on black was too hard to read and that shift+page up didn’t scroll in a gnu screen session. These seem like small annoyances, and they are, but they kept coming back and driving me nuts, and I’ve never seen anybody else really address them a way I liked. (Of course, there’s been a LOT of terminal emulator revival activity in the last decade, and like i said, I glance down the announcement posts but generally don’t actually try them, so maybe I missed something.) A few other things came up too, like copy/paste, window icons, and others, that I was able to hack up answers for after making the switch. The window/taskbar icons in particular are so vital to me now that I wonder how I ever got anything done without them before!
Important to note that I switch between laptop and desktop quite often, moving existing sessions between them, and I want it all to just work, including the programs I haven’t rewritten to support my custom stuff.
The usual answer to poor color contrast is to either have the application change its output, or change the entire terminal color palette. Neither are appealing to me: applications don’t have an existing way to change their palette when detaching/attaching screen sessions, so only my custom things could do it (and I sadly haven’t rewritten mutt and vim yet….), and changing the whole palette is throwing out the parts I like too.
So my answer was to adjust things on the display side: the terminal emulator core just stores it as color X on background Y. When it gets to the display, it says if X is unappealing to me on Y, change it to Z on Y instead, regardless of if the application set the background or if it just happened to be my default. It just works and brings me joy.
For keys, I see ghostty does the kitty keyboard protocol. I’m pretty sure this is fairly new; I went looking for something similar in 2013 and ended up making my own (based on the xterm modifyOtherKeys), with one important part: when the alternate screen is active, scroll commands go through this too.
So normal behavior: shift+page up scrolls the local terminal up. But when alt screen is on, shift+page up sends a shift+page up escape sequence to the application, which is then free to scroll itself or forward it down the next level. End result: when i attach some_session and shift+page up, it scrolls up the attached session! My existing habits just work even when migrating sessions. Joy brought.
These two things, plus the basics working, was enough for me to leave good old xterm behind in favor of my custom thing.
My session program is also how tabs work for me. My terminal emulator gui never implemented them; if I want tabs, I’ll open up the attach session inside. It prints the tabs inside the TUI, I can click on them to display them, or I can ctrl+a then 1,2,3 etc, similar to gnu screen. (The difference is I start at 1 instead of 0, so the keys on the physical keyboard align with the tabs on the screen.) Works pretty well. And fun fact: I can nest this as many times as I want (though I don’t want, since it is kinda awkward to use nested more than one layer deep, I can do it).
The sessions aren’t just tabs though: they have independent titles, icons, and some other settings. I often have many terminal sessions open at once, and want to pick them out quickly on my taskbar. The icon and title do this wonderfully. When I attach zoo for example, the window with that session attached shows the zoo logo, so I can find that project instantly. Beyond that, the “request attention” flag comes through the terminals into the session, so say I get a message on my IRC program, it can highlight that tab in the emulator and also highlight that session on the taskbar. I can quickly and easily set it to highlight on output events too - say I start a build and want to know when it is done while doing other things, I hit a hotkey and then the taskbar button will change colors when it is done…. even if I move to the laptop before the job is finished and want to monitor it there.
So this strikes enormous joy.
(Of course, not everything is in a long term session. In fact, most my terminal windows are very short lived - a hotkey in my window manager summons the terminal and I use this for tons of things. Might summon one just to check something quickly, or just to write a #note to self at the shell prompt to look back to later. My terminal starts instantly, just like old xterm - it must to support this kind of thing.)
Thinking about your ghostty with its native tabs, having a tui thing embedded isn’t going to be ideal for you. It is nice though, since say you putty in from a foreign Windows box and want to pull up one of your sessions, it is nice that it still at least somewhat works, but if you are on another Mac with ghostty, having your tabs work just as well over ssh as they do locally would be cool.
So I think the way I’d do this is kinda like take your tab controls and forward it through some application-specific escape sequences too. Ctrl+a 1, the gnu screen magic for selection tab #1, or clicking on that tab in the gui, or selecting it from the Mac menu bar, all can do the same thing. In fact, the Mac api offers a decent idea: these things send down a selector message that your application responds to. It could send down like a esc ? run selector “changeTab: 1” message. If the terminal application understands this - that is, if it is a nested session - it can send that through the whole chain. The mac implementation dispatches that to the gui window responder. The linux implementation does its own thing. When you attach to a new location, it first requests session information, which sends down like "tabs": [{"title": "whatever", "icon": "xxxx"}, ...], if it is in a compatible terminal, instead of drawing its own ui. Then the terminal can create that and finish the thing.
So you get all the benefits of your native tabs together with the session migration across computers that people like me swear by. That would probably be really cool.
(tho ngl, I wouldn’t switch anyway, since I’m so set in my ways, and besides, once I do it myself, I get attached to my little code baby.)
this ended up long, maybe I should have written my own blog post instead of a comment, oh well, just some ideas you might consider stealing too if you like any of them.
Eventually I’m tossing around the idea of an AWK implementation that uses Pike’s structural regex and capture groups instead of FS, but we’ll see if I get to it.
I haven’t decided if I will or not. I’m likely looking to unify my usage with agenix and maybe bitwarden for syncing. We’ll see though, I haven’t investigated too much yet. pa is nice and simple, so it works for scripts really well. It’s not quite clear to me how it decrypts the vault though? I haven’t dug through the code, but the fact that I can just do pa show ... without any sort of decryption/password step concerns me slightly and has prevented me from putting more in it.
I usually use this version, though I found some bugs with child processes getting orphaned improperly that might push me to either fork it or try out the plan9port version.
rc is much nicer to script than (ba)sh IMO, and I control all of my systems /shrug
I just made a Day 1 solution. Ryelang is borderline concatenative, but i will post it :)
Factor one is looking very nice … I just got lost at “ dup _ at 0 or * “
// edit: waiting approval. Anyhow … this is my solution on reddit megathread
Yes, totally! I did last year, and it took a lot of time. SQL (with mutual recursion)! So many other thoughts…
If I wasn’t working on SQL stuff, I would almost certainly take the opportunity to pick up more APL, K, Forth, or (personal fav at the moment) Joy. In fact, I strongly recommend Joy on the basis that it brought exactly that for me!
Here’s my attempt to sway you into the world of Factor. Before Joy’s creator, Manfred von Thun, sadly passed in 2011, he sent this heartfelt message honoring Factor’s creator, Slava Pestov, including:
I want to use this my first email to the group to do
what I should have done years ago. Slava Pestov has
designed and implemented his Factor language over
quite some time now. The result is a most impressive
piece of work. The design goes well beyond what I
ever dreamed of for Joy, and the implementation is
professional and clearly far superior than that of Joy.
There is already a huge library for all sorts of things.
And, very importantly, there are many users who are
contributing in one way or another.
. . .
And what should happen to Joy? With a far superior
alternative around, it would be pointless for me
to patch up the existing Joy either as the language
or in the implementation.
. . .
Should somebody else design a kind of Joy2? Maybe,
but probably not. It would have to be significantly
different from Factor to make it worthwhile.
I’ve peeked a bit, but only a very small bit, at Factor. I should say that what I liked about Joy was the act of implementing it, and realizing how little it needed to be, more than actually using it which I haven’t really done for much more than some recursive computations. I think the same is probably true of Forth; I don’t imagine I would enjoy using it too much longer after I implement it. My sense is that Factor means to solve for this, which is great!
I’ll put it on the queue to check out in more detail, but I think for me it competes on different terrain than Joy does, if that makes sense? As it moves from “oh boy, I am learning about concatenative idioms, user extensible dispatch, and seeing just how easy call/cc can be” to “ok, time to get real work done” it has to displace Rust, which .. oof. :D
Actually, I’m going to upgrade it from “put it on the queue” to “put it on the stack”! I’ve got https://factorcode.org open now. :D
I am lookng for recommendations which language to lear and use. I want something that expands my view, but is a bit more esoteric. Now I’ve progrmamed in Haskell, Clojure, Python, Rust, C, C#, PHP amongs others. Looking for some cool ideas. One of my ideas was Idris 2 or Koka. So please send me recommendations :)
How about Lil? It isn’t particularly engineered for speed, but it has a nice blend of features from functional languages and the APL family, it has first-class database-style tables and a SQL-like query language, it’s pretty handy at tokenizing messy input, and it can run in nearly any environment with a C compiler, a web browser, or an awk interpreter.
Last year I failed to do it in Rust. I ended up spending most of my time thinking about Rust rather than the problem at hand. So from now on I’ll be sticking to languages I’m already comfortable in. This year it will be Rust again.
ATS interleaves proof-level & value-level code while having dependent & linear types if you want to scratch those itches. Since you have done Haskell, another ML family language shouldn’t be too different on the syntax level (tho there are a lot of keywords). A read Introduction to Programming in ATS earlier this year from their documentation page.
I usually also decide I don’t have “enough time” after 3-5 days :). Interesting languages. Factor is where my username (and company name) comes from btw.
The thing about not having enough time is that once you miss a day, you have 4 tasks in one day, so you’re less motivated to do that potentially, and so on. I don’t think I’ve ever made it beyond day 10 for this reason.
There was one year where every 2nd day was referencing each other, but in other years you can easily skip single or multiple days… if you can get over the fact that there’s this gaping hole in your star count.. I know what you mean, and I prefer to go all in or not participate, but I still think you can kinda go casual and just do how much you feel like.
I remember being surprised when I learned you could skip a day. The interface may not make that (or may not have at the time… it’s been a while) obviously doable.
Yeah, I really enjoyed that year as well, seems like many others didn’t but it was really a lot of fun to tinker around with my intcode interpreter, I have written it in a couple of languages by now :)
I was amused to learn that Factor kind of reverses this approach: in Factor, nothing except f (false) is false, but f is also an empty sequence (so can be checked with empty? if you want to treat empty and false as the same case).
That was a fun read, and timely for me since I’ve been dabbling in many programming languages recently (it’s a silly bucket list item for me).
Obviously the article is based on the author’s experience, and they note that explicitly, but here are some languages that blew my mind, in addition to the those already in the article:
Forth (it’s so simple that I feel like I could implement most of it without any preparation)
K or J (array programming languages make a lot of frequent patterns into first class syntax
Also, if you don’t mind esoteric languages, I highly recommend:
Subleq (Turing complete instruction set… in a single instruction!)
Piet (a two-dimensional, graphical language that has similarities to Forth)
And I’d give Common Lisp an honorable mention for it’s condition+restarts system, as well as its REPL-driven workflow.
Agreed! Factor also has good documentation and a large community (as far as concatenative languages go). Others worth mentioning are PostScript, RetroForth, Min, and Uxntal.
I love first impressions that include “where the hell are loops? Oh, 10.times do { |i| ... }, sure, why not.” Personally, I find it hilarious.
That said, there’s a second impression that I wish more people got, where Enumerable/Iterator clicks. Because “everything is an object” you can dot chain laziness into the middle of something like the above.
In Haskell you write [1..] instead of (1..Float::INFINITY).lazy or (1..10_000_000).each.
You write filter even instead of select(&.even?).
A short lambda expression like {|x| x * 3} in Crystal can be abbreviated as an operator section in Haskell: (* 3).
You write take 3 instead of `filter(3).
No need to write .to_a.
Unfortunately, the “pipe right” operator isn’t in the Haskell core, you need to load a package. More modern functional languages have it built in (F#, Ocaml, Elixir, etc). The usual ASCII encoding of “pipe right” is |> but I prefer ⊳.
So the nice functional way of writing that Crystal expression is
Give them time. (To see the wisdom in functional languages.)
That said, anything list-based, per your example, is where Haskell’s simplicity shines. Not so much necessarily outside that use-case. See: module management, records, error handling, foldl and unexpectedly explosive memory consumption, Data.Text.IO (and dealing with character encoding in general), the lack of thoughtful namespacing, the need to abstract everything into graduate level mathematics as much as possible regardless of necessity (functors, monads, monoids, applicatives, etc.), updating fields in immutable structs (compare Elixir’s %{person | age: 30} with using the lens library in Haskell, etc.)
I’ve been looking at roc-lang and idris lately, which are both inspired by haskell (roc more by way of Elm first), but (hopefully) without the warts.
Yes, let’s use proper typography to make code look better on the screen when viewing and editing it. Including proportional fonts. (Note, I prefer × to · for multiplication.)
[1‥] ⊳ filter even ⊳ map (× 3) ⊳ take 3
Unfortunately, there aren’t many FOSS code editors that support proportional fonts. Emacs has a steep learning curve and intimidating reputation, so I’m experimenting with Kate right now (it has a vi mode).
While I’d encountered virtual sequences in Factor (like ranges and <evens>), I hadn’t yet touched lazy lists (or lists at all, apparently). So I used this example to give it a try:
I like the idea of choosing a hello-world task that fits the language. But there’s another take out there – bye bye hello world – that picked a slightly more advanced (single) task for all programming languages to implement. See also those folks’ proglangcast.
I’m not very fond of the temporal aspect of their suggested benchmark program. In C, sleep() involves system-dependent headers, in other languages it might require loading a package, and in many esolangs or golflangs it could only be approximated with a tuned delay-loop. Otherwise the task just seems like a mildly fiddlier equivalent of the more widely-recognized FizzBuzz program.
In the context of UI toolkits, there’s a tangentially-related benchmark called 7GUIs. For the most part it’s a pretty good way of showcasing how various UI systems work, though I think the 7th “build a spreadsheet component” exercise is too big and loosely-specified, as evidenced by the fact that many attempts at the benchmark elide it.
Lil expressions flow right-to-left, like most members of the APL family. A minor quality-of-life feature I’ve adopted in the various Lil REPLs is the convention of binding the last expression of each request’s result to the name
_
, which allows you to make “forward progress” interactively without backtracking to define intermediate variables every time. If you realize you’ll need something again, you can name it after evaluating the expression which produced it:Ivy does the same thing.
Nice! The Python REPL does the same thing, and it’s very handy.
I continue to be totally enamored by concatenative/tacit/stack-oriented/chained/pipelined/postfix/RPN/whatever-you-want-to-call it design, in particular its manifestation in Factor.
The given Uiua example (mercifully given using words rather than the symbols):
For all the talk about “forward” it’s uncomfortable to me how the Uiua evaluation within a line happens backward.
An equivalent in Factor, where
keep
is close toon
:But this pattern of doing two things in sequence to the same item is common enough that
bi
is handy:Maybe I’m being dense, but:
that’s one keystroke/key combo more.
Yes, it’s the basic example, but I find myself going to the middle of a pipe’d pipeline in my shell just as often, that’s what alt-left/ctrl-left or whatever are for. If I use vim bindings in an editor it’s also easier.
I may be completely missing the point, but my take is that for this to really matter it needed to be like 90% forward, where in practice it’s maybe 60% and so the perceived benefit is only “a little less going back”.
And if I sound like having a strong opinion, it’s because I have a mac as a work laptop for the first time and Home/End/ctrl-left/alt-left and so on behave differently than on Linux+Windows and I’m constantly failing to move, to a degree that I only notice now how I don’t notice it usually.
Yet, at the same time, pipes do feel much more satisfying to write to me than wrapping stuff in function calls. I feel like it has a lot to do with not just how you type the expression, but also how it looks in the end.
When you’re composing a pipe, you can see the pipe growing to the right. The left side remains as it was before. Wrapping something in a function call, on the other hand, means shifting the original expression to the right.
I feel like that incremental growth that happens with pipes is very satisfying.
Also half of the occurrences of people shouting “useless use of cat” are not grasping that the person who wrote it probably was just working incrementally from left to right ;)
So yeah, I’m not disagreeing per se - but I don’t really find it a meaningful difference.
Perhaps this isn’t the most motivating example, yeah.
I think that there’s a universe where, in the REPL, typing
|
right after evaluating an expression would wrap the previous expression in parentheses, put the cursor to the very left, and let you just type the function call.So I see this as more an issue with the status quo in many languages. I’m constantly finding myself wishing I could just continue transforming, and Uiua is the only language that keeps up. That and shell scripting for the most part. like 85% of my thinking is in this mode, but I can’t reflect that in my code.
I do agree about the value of dancing around with Emacs bindings, one of the big reasons for me to stay on a Mac.
It is this universe! In LSP/TreeSitter world this is almost a trivial feature to implement, and many do this:
.call
postfix).arg
postfix)In general, these days everyone has access to a powerful structured editor, not only lispers. It’s just that:
If you haven’t, I do suggest you give Factor a proper go!
Factor has been on my radar for a while! I’m having fun messing around with Uiua that Factor is a bit by the wayside.
To be honest what I think I really need is embeddable versions of these languages. So when I’m working in a Python REPL, I can pipe my data into the languages and work off of that for a while, and then escape back out to the real world in the end.
tried it for a couple minutes
imo it’s not good to the point that’ll make me consider switching from alacritty
but hey it’s always nice too see some competitions (and maybe this is the largest oss zig project in terms of loc i know)
No problem at all. Use what you love. May I ask why though? That information itself even if you don’t use Ghostty is super valuable to me.
I’m not that person, but I also gave it a try today, and found it pretty great that it’s nice and usable with only a few config options set (font, theme, disable gtk titlebar). Great work, and thanks for it!
The two immediate things I noticed keeping me off it for now: no sixel support, and some key combinations like
shift
+{home
,end
,pgup
,pgdown
} andctrl
+shift
+{left
,right
} don’t seem to register.Ah, the problem was that those keys are already bound to something by default, so
keybind = clear
fixes that problem! If it gets sixel support then I might try it full time.me another person who isn’t the one you replying to, but if you’re open to information from non-switchers, here’s my story…
I’ve never actually tried your program (once I write my own, there’s zero realistic chance of me switching to something else anyway, so I look at other things just to see if there’s any ideas i want to steal), but I have some ideas you might want to consider too.
Bottom line up front:
The long story:
For the last …. golly, over 11 years now, how time flies, I have used my own terminal emulator suite (in the D programming language, like virtually everything else i do lol). There’s three primary components: the terminal emulator core library, which is basically a big class with abstract methods the displays must implement, and a bunch of methods the display calls.
The next piece is the main gui implementation, so it does like
override void displayScreen(....) { drawText(yada yada yada); }
and thenwindow.onkeyevent = function(event) { emulator.sendKeyInput(event.key); }
and so on.Looks like ghostty does basically this same thing, so you know those parts I’m sure.
The final piece is the detachable session implementation (so a gnu screen/tmux replacement), which forks into two parts: one is the terminal emulator core in memory, with its methods implemented like
displayScreen() { nextTerminal.write(our contents); }
andnextTerminal.oninput = function() { ourCore.sendRawInput(...); }
, and the attach command, which manages whatnextTerminal
actually is. Making sure all my desired features could be reliably implemented and forwarded through this detached session drove a lot of the library api design.I also have a terminal client library - think ncurses replacement - that I wrote before the emulator, but since expanded to include support for my custom features, so when I make a program for myself, I can opt into whatever progressive enhancement I want. For example, its
getline
function works just fine on xterm. But if running on my terminal emulator, it enables its special mode for application mouse event reporting on the line with the cursor (and only on the line with the cursor). So you can click around there if you want, without breaking selecting text elsewhere. Just nice to provide the ez options at all levels (for people who use my lib). The detachable session thing uses this - it is a bridge of sorts between the client and server, a proxy really. … like literally a proxy, that’s the term I should use lol.So for a concrete example, I open up a terminal here,
attach some_session
, and middle click in it. The middle click event goes to theattach
client, which forwards it to the some_session terminal emulator (which is an in-memory instance of the library object). That object sees a middle click event, and does the normal branch: if in mouse reporting mode, send the event through the pty to the application, or if not in mouse reporting mode, it calls the virtualpasteRequested
method.The pasteRequested implementation for the session backend does not immediately try to get the clipboard contents off the OS api! The backend doesn’t know where it is attached yet, so it actually sends that request up one more level via one of the xterm escape sequences (
pty.write("\033]52;p;?\007");
though my code comment says “not quite compatible with xterm but kinda since xterm tends not to answer anyway” soooo maybe do it a bit differently, idk, works for me though). So now the attach client forwards this to the emulator it is attached to, which understands that escape sequence as “please send the clipboard contents”.If it is another nested emulator, it does that again, sending the escape sequence up one more level, until it gets to the actual gui window, which finally fetches from the clipboard and sends it back down the chain. The attach client frontend is a terminal program that activates the alternate screen, bracketed paste, and such, so it now sees the bracketed paste content and is able to send that down the chain to its emulator core as a paste event, which can forward to its application, which may or may not have requested bracketed paste.
Each level unwraps and rewraps the escape sequences to higher level event objects and vise versa. Forwarding the escape sequences directly sometimes works, but sometimes breaks since there might be some need for graceful degradation, I prefer to make them all methods in the api so it can make that decision at the appropriate level. This means all my cool extension features work well even when migration sessions between desktop and laptop - no more loss through the terminal multiplexing proxy, so long as it is my code on both ends (and if it is back in a regular xterm or putty, it doesn’t work as well, but it does still work, so im never locked out either).
Previously, I was primarily an xterm+screen user, and switched to my own stuff in 2013. The two main reasons why I wrote my own terminal emulator was that yellow on white or blue on black was too hard to read and that shift+page up didn’t scroll in a gnu screen session. These seem like small annoyances, and they are, but they kept coming back and driving me nuts, and I’ve never seen anybody else really address them a way I liked. (Of course, there’s been a LOT of terminal emulator revival activity in the last decade, and like i said, I glance down the announcement posts but generally don’t actually try them, so maybe I missed something.) A few other things came up too, like copy/paste, window icons, and others, that I was able to hack up answers for after making the switch. The window/taskbar icons in particular are so vital to me now that I wonder how I ever got anything done without them before!
Important to note that I switch between laptop and desktop quite often, moving existing sessions between them, and I want it all to just work, including the programs I haven’t rewritten to support my custom stuff.
The usual answer to poor color contrast is to either have the application change its output, or change the entire terminal color palette. Neither are appealing to me: applications don’t have an existing way to change their palette when detaching/attaching screen sessions, so only my custom things could do it (and I sadly haven’t rewritten mutt and vim yet….), and changing the whole palette is throwing out the parts I like too.
So my answer was to adjust things on the display side: the terminal emulator core just stores it as color X on background Y. When it gets to the display, it says if X is unappealing to me on Y, change it to Z on Y instead, regardless of if the application set the background or if it just happened to be my default. It just works and brings me joy.
For keys, I see ghostty does the kitty keyboard protocol. I’m pretty sure this is fairly new; I went looking for something similar in 2013 and ended up making my own (based on the xterm
modifyOtherKeys
), with one important part: when the alternate screen is active, scroll commands go through this too.So normal behavior: shift+page up scrolls the local terminal up. But when alt screen is on, shift+page up sends a shift+page up escape sequence to the application, which is then free to scroll itself or forward it down the next level. End result: when i
attach some_session
and shift+page up, it scrolls up the attached session! My existing habits just work even when migrating sessions. Joy brought.These two things, plus the basics working, was enough for me to leave good old xterm behind in favor of my custom thing.
My session program is also how tabs work for me. My terminal emulator gui never implemented them; if I want tabs, I’ll open up the attach session inside. It prints the tabs inside the TUI, I can click on them to display them, or I can ctrl+a then 1,2,3 etc, similar to gnu screen. (The difference is I start at 1 instead of 0, so the keys on the physical keyboard align with the tabs on the screen.) Works pretty well. And fun fact: I can nest this as many times as I want (though I don’t want, since it is kinda awkward to use nested more than one layer deep, I can do it).
The sessions aren’t just tabs though: they have independent titles, icons, and some other settings. I often have many terminal sessions open at once, and want to pick them out quickly on my taskbar. The icon and title do this wonderfully. When I
attach zoo
for example, the window with that session attached shows the zoo logo, so I can find that project instantly. Beyond that, the “request attention” flag comes through the terminals into the session, so say I get a message on my IRC program, it can highlight that tab in the emulator and also highlight that session on the taskbar. I can quickly and easily set it to highlight on output events too - say I start a build and want to know when it is done while doing other things, I hit a hotkey and then the taskbar button will change colors when it is done…. even if I move to the laptop before the job is finished and want to monitor it there.So this strikes enormous joy.
(Of course, not everything is in a long term session. In fact, most my terminal windows are very short lived - a hotkey in my window manager summons the terminal and I use this for tons of things. Might summon one just to check something quickly, or just to write a
#note to self
at the shell prompt to look back to later. My terminal starts instantly, just like old xterm - it must to support this kind of thing.)Thinking about your ghostty with its native tabs, having a tui thing embedded isn’t going to be ideal for you. It is nice though, since say you putty in from a foreign Windows box and want to pull up one of your sessions, it is nice that it still at least somewhat works, but if you are on another Mac with ghostty, having your tabs work just as well over ssh as they do locally would be cool.
So I think the way I’d do this is kinda like take your tab controls and forward it through some application-specific escape sequences too.
Ctrl+a 1
, the gnu screen magic for selection tab #1, or clicking on that tab in the gui, or selecting it from the Mac menu bar, all can do the same thing. In fact, the Mac api offers a decent idea: these things send down a selector message that your application responds to. It could send down like a esc ? run selector “changeTab: 1” message. If the terminal application understands this - that is, if it is a nested session - it can send that through the whole chain. The mac implementation dispatches that to the gui window responder. The linux implementation does its own thing. When you attach to a new location, it first requests session information, which sends down like"tabs": [{"title": "whatever", "icon": "xxxx"}, ...]
, if it is in a compatible terminal, instead of drawing its own ui. Then the terminal can create that and finish the thing.So you get all the benefits of your native tabs together with the session migration across computers that people like me swear by. That would probably be really cool.
(tho ngl, I wouldn’t switch anyway, since I’m so set in my ways, and besides, once I do it myself, I get attached to my little code baby.)
this ended up long, maybe I should have written my own blog post instead of a comment, oh well, just some ideas you might consider stealing too if you like any of them.
All I do is map capslock to backtick/tilde, with backtick as my tmux key.
But I dream of a TEX Shinobi or Kodachi.
Trying to only list some that aren’t already listed:
Some of my own:
Eventually I’m tossing around the idea of an AWK implementation that uses Pike’s structural regex and capture groups instead of FS, but we’ll see if I get to it.
ayy i wrote pa - any reason for wanting to swap it out ooc?
I haven’t decided if I will or not. I’m likely looking to unify my usage with agenix and maybe bitwarden for syncing. We’ll see though, I haven’t investigated too much yet.
pa
is nice and simple, so it works for scripts really well. It’s not quite clear to me how it decrypts the vault though? I haven’t dug through the code, but the fact that I can just dopa show ...
without any sort of decryption/password step concerns me slightly and has prevented me from putting more in it.Thanks for introducing me to tsk, I like it a lot!
Glad you like it! (I’m the author)
Link?
The original plan9 version has docs here.
I usually use this version, though I found some bugs with child processes getting orphaned improperly that might push me to either fork it or try out the plan9port version.
rc
is much nicer to script than (ba)sh IMO, and I control all of my systems /shrugI hope this doesn’t break a rule, but if you’re using a concatenative language, please consider posting solutions at c/concatenative.
I just made a Day 1 solution. Ryelang is borderline concatenative, but i will post it :) Factor one is looking very nice … I just got lost at “ dup _ at 0 or * “
// edit: waiting approval. Anyhow … this is my solution on reddit megathread
Yes, totally! I did last year, and it took a lot of time. SQL (with mutual recursion)! So many other thoughts…
If I wasn’t working on SQL stuff, I would almost certainly take the opportunity to pick up more APL, K, Forth, or (personal fav at the moment) Joy. In fact, I strongly recommend Joy on the basis that it brought exactly that for me!
Here’s my attempt to sway you into the world of Factor. Before Joy’s creator, Manfred von Thun, sadly passed in 2011, he sent this heartfelt message honoring Factor’s creator, Slava Pestov, including:
I’ve peeked a bit, but only a very small bit, at Factor. I should say that what I liked about Joy was the act of implementing it, and realizing how little it needed to be, more than actually using it which I haven’t really done for much more than some recursive computations. I think the same is probably true of Forth; I don’t imagine I would enjoy using it too much longer after I implement it. My sense is that Factor means to solve for this, which is great!
I’ll put it on the queue to check out in more detail, but I think for me it competes on different terrain than Joy does, if that makes sense? As it moves from “oh boy, I am learning about concatenative idioms, user extensible dispatch, and seeing just how easy
call/cc
can be” to “ok, time to get real work done” it has to displace Rust, which .. oof. :DActually, I’m going to upgrade it from “put it on the queue” to “put it on the stack”! I’ve got https://factorcode.org open now. :D
Ok yes I’m already in love with the
::
word to bind the stack elements spelled out in the stack effect documentation.I am lookng for recommendations which language to lear and use. I want something that expands my view, but is a bit more esoteric. Now I’ve progrmamed in Haskell, Clojure, Python, Rust, C, C#, PHP amongs others. Looking for some cool ideas. One of my ideas was Idris 2 or Koka. So please send me recommendations :)
How about Lil? It isn’t particularly engineered for speed, but it has a nice blend of features from functional languages and the APL family, it has first-class database-style tables and a SQL-like query language, it’s pretty handy at tokenizing messy input, and it can run in nearly any environment with a C compiler, a web browser, or an awk interpreter.
SWI-Prolog, Forth (or probably Factor), Unison, J, Rebol
Forth and Factor are both concatenative languages with a stack but otherwise are so different that I wouldn’t compare them like that.
Ditto, I’d try a Forth. Or maybe Io! It’s not super active, but it’d be a nice way to stretch the brain a little.
I would also recommend Prolog, but perhaps https://www.scryer.pl/ Scryer Prolog instead of SWI, since it’s more compliant with the Prolog standard
There was a call and a leaderboard on the Picat mailing list if that’s your flavour.
Last year I failed to do it in Rust. I ended up spending most of my time thinking about Rust rather than the problem at hand. So from now on I’ll be sticking to languages I’m already comfortable in. This year it will be Rust again.
Something concatenative.org maybe? :)
I predictably second refaktor’s suggestion, with Factor in particular.
I did Pharo Smalltalk one year!
ATS interleaves proof-level & value-level code while having dependent & linear types if you want to scratch those itches. Since you have done Haskell, another ML family language shouldn’t be too different on the syntax level (tho there are a lot of keywords). A read Introduction to Programming in ATS earlier this year from their documentation page.
Factor would be an interesting choice. I’ll probably try in Python, Factor and Forth (Retroforth).
I usually also decide I don’t have “enough time” after 3-5 days :). Interesting languages. Factor is where my username (and company name) comes from btw.
The thing about not having enough time is that once you miss a day, you have 4 tasks in one day, so you’re less motivated to do that potentially, and so on. I don’t think I’ve ever made it beyond day 10 for this reason.
There was one year where every 2nd day was referencing each other, but in other years you can easily skip single or multiple days… if you can get over the fact that there’s this gaping hole in your star count.. I know what you mean, and I prefer to go all in or not participate, but I still think you can kinda go casual and just do how much you feel like.
I remember being surprised when I learned you could skip a day. The interface may not make that (or may not have at the time… it’s been a while) obviously doable.
I loved Intcode! It was introduced in 2019 on day 2, and then extended or reused on day 5 and every odd-numbered day after that.
Yeah, I really enjoyed that year as well, seems like many others didn’t but it was really a lot of fun to tinker around with my intcode interpreter, I have written it in a couple of languages by now :)
I was amused to learn that Factor kind of reverses this approach: in Factor, nothing except
f
(false) is false, butf
is also an empty sequence (so can be checked withempty?
if you want to treat empty and false as the same case).Another cool project that could probably use some help: https://github.com/ponyorm/pony
Hey if you’re taking requests, here are some of mine:
That was a fun read, and timely for me since I’ve been dabbling in many programming languages recently (it’s a silly bucket list item for me).
Obviously the article is based on the author’s experience, and they note that explicitly, but here are some languages that blew my mind, in addition to the those already in the article:
Also, if you don’t mind esoteric languages, I highly recommend:
And I’d give Common Lisp an honorable mention for it’s condition+restarts system, as well as its REPL-driven workflow.
If you like Forth and haven’t tried Factor yet, you might find it lots of fun!
Agreed! Factor also has good documentation and a large community (as far as concatenative languages go). Others worth mentioning are PostScript, RetroForth, Min, and Uxntal.
Does it support sixel graphics? What has kept me on foot has been that yazi is able to preview images in the terminal.
Ghostty uses the Kitty graphics protocol instead of sixel.
Thanks, that would be one reason for me to stick to Wezterm, as tmux supports sixel but not the Kitty protocol.
I love first impressions that include “where the hell are loops? Oh,
10.times do { |i| ... }
, sure, why not.” Personally, I find it hilarious.That said, there’s a second impression that I wish more people got, where
Enumerable
/Iterator
clicks. Because “everything is an object” you can dot chain laziness into the middle of something like the above.An example in Ruby would be
An example in Crystal would be
Kind of neat.
The author probably didn’t talk about this because we do this in Rust as well, so they’re already used to that being a thing.
This looks a noisy parody of Haskell.
[1..]
instead of(1..Float::INFINITY).lazy
or(1..10_000_000).each
.filter even
instead ofselect(&.even?)
.{|x| x * 3}
in Crystal can be abbreviated as an operator section in Haskell:(* 3)
.take 3
instead of `filter(3)..to_a
.Unfortunately, the “pipe right” operator isn’t in the Haskell core, you need to load a package. More modern functional languages have it built in (F#, Ocaml, Elixir, etc). The usual ASCII encoding of “pipe right” is
|>
but I prefer⊳
.So the nice functional way of writing that Crystal expression is
It’s in
Data.Function
as(&)
: https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-Function.html#v:-38-Cool, thanks!
Give them time. (To see the wisdom in functional languages.)
That said, anything list-based, per your example, is where Haskell’s simplicity shines. Not so much necessarily outside that use-case. See: module management, records, error handling,
foldl
and unexpectedly explosive memory consumption,Data.Text.IO
(and dealing with character encoding in general), the lack of thoughtful namespacing, the need to abstract everything into graduate level mathematics as much as possible regardless of necessity (functors, monads, monoids, applicatives, etc.), updating fields in immutable structs (compare Elixir’s%{person | age: 30}
with using thelens
library in Haskell, etc.)I’ve been looking at
roc-lang
andidris
lately, which are both inspired by haskell (roc more by way of Elm first), but (hopefully) without the warts.There are endless and beginnless ranges in Ruby and Crystal, i.e.
(..10)
and(1..)
works.In Ruby
#filter
is the same as#select
.The Crystal example could be written as
You mean just like everything else?
If you’re going Unicode, why not go Unicode all the way?
Yes, let’s use proper typography to make code look better on the screen when viewing and editing it. Including proportional fonts. (Note, I prefer × to · for multiplication.)
[1‥] ⊳ filter even ⊳ map (× 3) ⊳ take 3
Unfortunately, there aren’t many FOSS code editors that support proportional fonts. Emacs has a steep learning curve and intimidating reputation, so I’m experimenting with Kate right now (it has a vi mode).
Very nice!
While I’d encountered virtual sequences in Factor (like ranges and
<evens>
), I hadn’t yet touched lazy lists (or lists at all, apparently). So I used this example to give it a try:The lack of loops (well, ruby actually has loops but you will be cencured if you use them) solves so many problems and I love it so much
I like the idea of choosing a hello-world task that fits the language. But there’s another take out there – bye bye hello world – that picked a slightly more advanced (single) task for all programming languages to implement. See also those folks’ proglangcast.
I’m not very fond of the temporal aspect of their suggested benchmark program. In C,
sleep()
involves system-dependent headers, in other languages it might require loading a package, and in many esolangs or golflangs it could only be approximated with a tuned delay-loop. Otherwise the task just seems like a mildly fiddlier equivalent of the more widely-recognized FizzBuzz program.In the context of UI toolkits, there’s a tangentially-related benchmark called 7GUIs. For the most part it’s a pretty good way of showcasing how various UI systems work, though I think the 7th “build a spreadsheet component” exercise is too big and loosely-specified, as evidenced by the fact that many attempts at the benchmark elide it.
If anyone wants to try some CLI conversion between NestedText and YAML, TOML, JSON and JSON Lines, I made NestedTextTo.
It uses the official Python implementation, and yamlpath to help you cast nodes to types supported by those other formats.
Very cool! Might it eventually be able to inject entries into existing YAML without clobbering the comments, or will it strictly be about extraction?
I’m a big fan and user of an apparently unrelated yamlpath. You might want to note that it’s unrelated in the readme.