HNHacker News
TopNewBestAskShowJobs

cb321

1,490 karma · joined July 25, 2020

submissionscomments
cb321··on Python with braces. Because Python is awesome, but whitespace is awful
Nim actually already supports using `()` as braces most of the time for most constructs. So, in this sense it is more like Haskell than Python, though the latter is more well known.

Nim is also more "expressional" than Python in many ways. So, for example, in Nim you can say:

    let x = (try: dict[key] except: 2)
    for i in 1..x: (
      echo i
    )
Most users hate how that looks, though, much as most users of bracist languages also hate:

    int foo(char bar) {
        int c = 0;
        for (char i = 0; i < bar; i++) c++;
        return c; } // could be many '}' here
In reality, these discussions feel more like style guide wars, reformatted as PLang syntax wars, pun intended.
cb321··on Zoxide: A Better CD Command
Once you have a little frecency command-line utility like https://github.com/c-blake/adix/blob/master/util/lfreq.nim (inspired by this very HN thread!) and then either fzf, vip (https://github.com/c-blake/bu/blob/main/doc/vip.md), or whatever, the essence of the idea is really just two lines of Zsh code (https://github.com/c-blake/bu/blob/main/doc/vip.md#combining...). It's only a couple dozen more to have a more robust/complete system, though with shared-across-shells PWD histories and long-term saving somewhere with lock files.

As another example of why a simple CL utility for frecency alone is nice, you can basically replicate https://github.com/kantord/frecenfile with a shell 1-liner:

    git log --pretty=format: --name-only | tac | sed '/^$/d' | lfreq -n9 -o.999
In a little timing test I did this morning, that was about 270X faster than the git log itself on the Linux kernel (61 seconds to git log, but only 227 ms to `lfreq` it).

Besides all that, there's also the distributed-with-Zsh `cdr` (`man zshcontrib` or similar to read all about that).

cb321··on Zoxide: A Better CD Command
`hash -d` (aka "named directories") is surely an alternative and if that suits you, by all means.

I see at least two downsides: 1) now you have to remember to say `make -C ~c` { not `make -C $c` which I think most would find more natural } 2) the hash cannot be exported to inheriting subprocesses like a regular scalar $var.

Of course, 1) is kind of weak since you can also use "~var" most places. It's just not as familiar to many as $var.

One notable equivalency is that Zsh prompt escapes for $PS1 and friends like %~ would treat both the same - converting them to a "~c" inside your prompt.

So, in terms of "looking like what you type", that maybe makes ~c better. Maybe there is some setopt to make Zsh expand %~ as $c or $dd? Not sure. There are a lot of setopts.

Anyway, I actually use the exporting feature to non-Zsh subprocesses myself. So, I'm pretty locked-in to vars not just hash entries.

cb321··on Zoxide: A Better CD Command
Glad to be appreciated. You might also enjoy this snippet from my zshrc:

    my-expand() BUFFER=${(e)BUFFER} CURSOR=$#BUFFER
    zle -N my-expand        # Make Alt-E expand environ vars w/o val escape
    bindkey '^[e' my-expand # Allows eVar "short cuts" for history !!:gs etc
It's vaguely related in that it lets you put a variable in the main Zsh Line Editor (ZLE) buffer (with the leading `$`) and then press Alt-E to expand it. ('e' for E)nvironment-E)xpand).

As per the comment I did this so that I could have "history substitution expressions" stored in variables, then Alt-E - look (& maybe edit) & ENTER.

The history syntax itself is very cryptic, derived from early 1980s BSD csh. As with most such crypticnesses, figuring it out once, storing it somewhere you can remember, and expanding it on demand is a not awful way to learn it.

But besides cryptic history directives you could also use it for the `$a<Alt-E>` above to "see before you do". You need an extra `$`, though, as written. It would be pretty easy to auto-add a `$` prefix in `my-expand`, though (with Alt-E just becoming a sort of different kind of TAB for expansion rather than completion).

cb321··on Zoxide: A Better CD Command
In Zsh with `setopt autocd cdablevars`, shell variables more or less work like cd-aliases. You could also just add `a=projectA; b=projectB; c=projectC` to your `~/.zprofile` and then at prompts type 1-letter commands `a`, `b`, `c`.

One added bonus of such file tree bookmarks via variables (over a similar `alias a='cd foo'`) is that if you get muscle-memory/active memory for those few abbreviations other use cases like `make -C $a` also work. I usually leave `$hb` & `$lb` set to `$HOME/bin` and `/usr/local/bin`, for example.

cb321··on Murex – An intuitive and content aware shell for a modern command line
In that case, since you are already de-duping "externally", you might play with `setopt HIST_IGNORE_ALL_DUPS HIST_IGNORE_DUPS HIST_SAVE_NO_DUPS` combinations. It's been many years since I looked at it, but I think these conspire with large saved histories to slow things down a lot at startup/initial history parse.

I don't even recall if it's necessary or was just the simple algorithm. So, you might actually be able to get Zsh fixed if there is some quadratic thing that can be turned linear with a hash table. The Zsh mailing list is quite accommodating in my experience.

cb321··on GNU Midnight Commander
It's 10x slower than a more specialized command [1] for me, but adding recursion only requires adding one extra asterisk (`**` instead of `*`) and maybe a D if you want to include dot files or triple star if you want to follow symlinks:

    $ date; print -rl -- **(om[1].D);date; newest -n4 -r0 $HOME
    Wed Sep 17 12:48:53 EDT 2025
    .config/mozilla/firefox/p9/bounce-tracking-protection.sqlite
    Wed Sep 17 12:49:25 EDT 2025
    /u/p9/.config/mozilla/firefox/p9/permissions.sqlite
    /u/p9/.config/zsh/history
    /u/p9/.config/mozilla/firefox/p9/places.sqlite-wal
    /u/p9/.config/mozilla/firefox/p9/bounce-tracking-protection.sqlite
    *newest -n4 -r0 $HOME
     Time: 1.882365 (u) + 1.318166 (s)=3.215131 (99%) mxRSS 139 MiB
Not sure how to change to get most recent 4 or whatever in the Zsh style (since, you know, that'd be 10x slower..)

[1] https://github.com/c-blake/bu/blob/main/doc/newest.md*

cb321··on Murex – An intuitive and content aware shell for a modern command line
That sounds way too long. Mine takes like 15 ms on a 2015 cpu and I activate zsh-syntax-highlighting and new style completion and everything, but yeah oh-my-zsh often adds nutso overhead. Anyway, I suggest you profile your zsh start-up. Here's one copy-paste friendly way to do that:

    (PS4='+$EPOCHREALTIME ' zsh -licx exit)2>err
    era=$(grep '^+[1-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].[0-9]*' <err |
      head -c6) # c6 here rounds to 100_000 seconds (eg +17483xxxyy)
    awk '/^\'${era}'[0-9][0-9][0-9][0-9][0-9]\.[0-9]*/{
         if (c) printf "%.06f %s\n", $1 - t0, c; t0 = $1; c = $0 }
         END { printf "%.06f %s\n", 0, c; }' < err | sort -g > startup-profile
(Note for $EPOCHREALTIME to work you need a `zmodload zsh/datetime` somewhere early on. I might suggest at the top of `$ZDOTDIR/.zshenv` for this kind of thing.)

Also, if something seems limited by "just parsing", you can usually speed that up a lot with `zcompile`. I do that with a `.zcompdump.zwc` and a `digraphs.zsh.zwc`.

EDIT: I noticed myself that really large HISTSIZE (in the 100s of thousands, and with such limit realized) combined with de-duplication seems to be a bad combination. I just lowered my HISTSIZE with a when-too-big spool-off for longer term history/cold storage.

cb321··on The Titania Programming Language
A lot of people in this subthread are echoing this, but it's at most maybe very slightly easier. For example, TinyCC is a one-pass C compiler and yet C has sub-scopes int foo() { /.../ { int x; /.../ } }. The same could be said of Ken Thompson's C compiler which I believe was also one-pass.

Does Titania not have/intend to have lexically local subscopes? That would seem very un-Wirthian to me.

cb321··on A review of Nim 2: The good and bad with example code
Also, very cool. For example, https://github.com/SciNim/Measuremancer has Nim make the utf8 ± character be an operator that constructs an uncertain number with which to do error propagation arithmetic upon. So, you can, e.g. say (50±2)*(25±3) in the code and that will give 1250 ± 158 as an answer and if you use the cligen/strUt.formatUncertain that will even get correctly rounded to 1250 ± 160 (2 digits in the uncertainty place { configurable to 1 or 3 or whatnot or even parens notation like 1.25(16)e3 a la The Particle Data Group notation. }).
cb321··on A review of Nim 2: The good and bad with example code
Like literally anything, it will depend upon how much your code does, what libraries it uses and so on, but here's a trivial little example to at least dispell a worry of multi-megabyte outputs for trivial things:

    echo echo 1 > j.nim
    nim js j.nim
    node j.js
    >>> see 1\\n <<<<
    ls -l j.js
    >>> 36636 Sep  1 12:54 j.js <<<
    nim js -d:release j.nim
    ls -l j.js
    >>> 11369 Sep  1 12:56 j.js <<<
So, with -d:release stripping away a lot of debugging logic, it's not so bad. Even with d:release there is probably ~50% of the text of that j.js that is just C comments which could be trivially stripped away. E.g., cpp<j.js|wc -c gives 6350 for that very same 11369 file. There are js minification things one could also run on the output. People do complain about this, but people complain a lot. It's probably not so uncompetitive for less trivial programs that do a little bit more work, both minified, apples-to-apples care & all that.
cb321··on A review of Nim 2: The good and bad with example code
Nim also has term rewriting macros (e.g. https://scripter.co/notes/nim/#term-rewriting-macros) which can transform patterns. The relevance is that you could combine that with `||=` to probably get whatever short-circuit|not semantics you want on the RHS. Or also do bignum/matrix libraries where arithmetic can be streamlined (e.g. jumbo operations matched to convert N passes into 1-pass), { at least potentially. Often the scale matters as in fits in "available" L1 then many passes might be more autovec friendly and so faster, or doesn't fit then one pass is much faster. It all depends, etc., etc. }
cb321··on A review of Nim 2: The good and bad with example code
With `nim cpp` the Nim compiler actually just generates C++ from the Nim source for the backend to compile. So, calling C++ code is just emitting the calls at a C++ source level and so is straightforward. The situation with Rust "sharing" LLVM is very different, as that is not a source-to-source compiler.

C++ code calling Nim code is also not usually as straightforward. So, "fantastic" here may apply only in one call direction.

cb321··on A review of Nim 2: The good and bad with example code
The "dual" of this type-dispatch of template/macros is that the scoping rules also allow you to use a template or a macro to define a bunch of things which are only "in effect" in a sub-scope, like, e.g. that old C hazard of pointer arithmetic - "defined but contained": https://forum.nim-lang.org/t/1188#7366

In a lot of little ways, Nim is a lot like a statically typed Lisp with a vaguely Python-ish surface syntax, although this really doesn't give enough credit to all the choice one has writing Nim code.

cb321··on A review of Nim 2: The good and bad with example code
Yeppers - https://github.com/treeform/nim_emscripten_tutorial

FWIW, people have been doing that for about as long as there's even been an emscripten, but the article is pointing out the lack of more tight integration with stdlib/std compilation toolchains. I would say evolving/growing the stdib in general is a pain point. Both the language and compiler are more flexible than most, though. So, this matters less in Nim that it might otherwise.

cb321··on A review of Nim 2: The good and bad with example code
This is a decent overview, but misses a few nice things. Interested readers should not assume it is exhaustive (generally something they should not assume..)

E.g., because the feature is so rare (controversial?) it doesn't get mentioned much, but you can also define your own operators in Nim. So, if you miss bitwise `|=` from C-like PLangs, you can just say:

    proc `|=`*[T,U](a: var T, b: U) = a = a or b
Of course, Nim has a built in `set[T]` for managing bit sets in a nicer fashion with traditionally named set theoretic operators like intersection, union, etc. https://github.com/c-blake/procs makes a lot of use of set[enum] to do its dependency analysis of what Linux /proc files to load and what fields to parse, for example (and is generally much faster than C procps alternatives).

This same user-defined operator notation for calls can be used for templates and macros as well which makes a number of customized notations/domain specific languages (DSLs) very easy. And pragma macros make it easy to tag definitions with special compile-time behaviors. And so on.

cb321··on I'm working on implementing a programming language all my own
You would probably enjoy the UFCS https://en.wikipedia.org/wiki/Uniform_function_call_syntax of Nim, D, etc. Basically `h.g.f(x)` or in Nim you can drop the parens and say `h.g.f x` { but it may not scale past a single argument }. This tends to be only "an option" - more on the "allow/enable" side than the "force them" to do it side, though.
cb321··on The Lobster Programming Language
GNU indent was already at version 1.9.1 by 1994: https://ftp.gnu.org/gnu/indent/

If you grab that version and unpack it and look at /OChangelog then it seems to date back until at least 1989, same as Python itself.

That was for C source, of course. I expect there were pre-GNU indent variants, perhaps posted on comp.sources.unix and maybe some commercial things as part of very expensive compiler packages.

I would say that running autoformatters in any kind of routine way was pretty rare. EDIT: but I think ascribing the language design to commonality or not is probably ahistorical. Even today it's a rather passionate debate. And even at the time, Lisp - the poster child of copy-paste friendly PLangs - was routinely autoformatted within Emacs', but that was not enough for people to not find Lisp code "ugly".

cb321··on Uncertain<T>
If you are in an even more "approximate" mindset (as opposed to propagating by simulation to get real world re-sampled skewed distributions, as often happens in experimental physics labs, or at least their undergraduate courses), there is an error propagation (https://en.wikipedia.org/wiki/Propagation_of_uncertainty) simplification for "small" errors thing you can do. Then translating "root" errors to "downstream errors" is just simple chain rule calculus stuff. (There is a Nim library for that at https://github.com/SciNim/Measuremancer that I use at least every week or two - whenever I'm timing anything.)

It usually takes some "finesse" to get your data / measurements into territory where the errors are even small in the first place. So, I think it is probably better to do things like this Uncertain<T> for the kinds of long/fat/heavy tailed and oddly shaped distributions that occur in real world data { IF the expense doesn't get in your way some other way, that is, as per Senior Engineer in the article }.

cb321··on What are OKLCH colors?
Your link itself admits the 0.05 makes it a different formula. Both Y and L* go to zero for hard black which is a very common color (the most common for me) and would be infinite with black in there. I disagree this is all "not real".

The 2x2 table in that contrast experiments link I sent enumerates some differences along the edge cases { even with just |diff|s. }. Just empirically if you change that 0.05 to 0.02 or 0.10 things change "a lot" in terms of all the edge cases. You can try fiddling with running that Python script yourself and see.

Also, I believe the project of an actual "contrast measurement" - not merely threshold checking - is a worthy goal. I think it would be good to be able to say how bad, and for that the specific monotonic transformation absolutely matters, and again, I expect the color space designer people have opinions on this very worth listening to. I think they are targeting differences in the numbers being the most meaningful thing.

All that said, I did like your George Box quote. :-) I just don't think dismissing the problem is a great solution here. I'm not sure there is a great solution. But you & anyone are always free to find any problem uninteresting. I mean, you could also find all the color space distinctions of TFA similarly "no real difference".

cb321··on What are OKLCH colors?
I don't think you & I have much disagreement here as I like the way you write about approximations and edge cases and things involving human judgement calls and "both not either" kinds of testing. The WhyAPCA document you link to also includes language to such effect with sliding scales over regions & such. Me - I'm mostly asking questions not offering answers. That said, to correct the record..

>These examples are using the WCAG2 contrast algorithm which is well known

Only one of the 4 tables shown is the thing you say is the known-to-be-flawed WCAG2 one. Some counterxamples are listed for all 4 formulas, though, 2 of which use the CIE Lightness (which, sure, is probably different, but I believe the CIE L is what APCA is based upon - in spite of so..many..words on their doc pages they often just say "lightness").

------------------------

Another point of those 4 tables, perhaps more clear when looking at the python script, is whether "numerical ratio" vs abs(difference) is better. It seems to me that color space designers, like this OKLCH, are going after "perceptual linearity" which suggests abs(diff) is far more appropriate than a "ratio" which has "near zero" troubles (and zero & one are downright seductive numbers for perceptual lightness scales).

I certainly should learn more about it, but various "click through" APCA things I've seen seem to speak in ratio terms like "10 times the contrast" (though admittedly that only assumes some scale for contrast not that it's formulated as a ratio - it's just suggestive). So, I should probably look more into it before actually offering a critique, but it still has the feeling of "cross purposes" - using some color space axis designed for [0,1] linearity differences instead for ratios within that axis. When I tried using the WCAG2 one I was kind of stunned how sensitive everything was to what should have been a kind of "arbitrary adjustment" to handle near-zero.

I might wonder what designers of color spaces actually have to say about this ratio vs. difference issue if you know of any articles. You seem knowledgeable. The spaces seem literally designed for differences to me.

cb321··on What are OKLCH colors?
As per discussion of this new OKLCH idea in TFA - Are we even sure we have great formulas for measuring/defining contrast in the first place? Do you agree / disagree with any of the counterexamples over at: https://github.com/tattoy-org/contrast-experiments
cb321··on Why Nim?
I would also push back (agreeing with @TylerE, but linking in this part of the subthread seems best). It all depends, but sometimes macros change the division of labor between library authors and library users so that they specifically have to learn less. For example, https://github.com/c-blake/cligen makes it so that you don't really have to learn an "API" to roll a nice CLI on top of a procedure. You just need to learn literally 2 or maybe 3 names: "cligen", "dispatch", and maybe "help={}". Everything else, you already know from knowing Nim basics (or even Python basics). (The dispatchGen macro itself is rather a monster of static introspection and code generation and not at all pedagogical -- mostly from filling like 100 user requests on closed issues.)

It's just important to keep in mind that library / framework authors, library / framework users, and final end users are not all the same person or even type of person. Metaprogramming enables the first group the most, but can deliver benefits to the latter two groups by easing their lives quite a bit. This is easy to lose track of in a niche language like Nim where all 3 groups might well be the same human for most of the life of some project.

This partly also relates to how much of a "full stack debugger (of either correctness or performance or both)" personality one has. Then there are even more layers -- like all the levels in the language / compiler itself down to the CPU and beyond (such as "u-code" & micro-architectural resource sharing).

What macros mostly do is enable fluid, simple syntax for things either too rare, too niche, too unanticipated or too disagreed upon to be in "the main language", whatever that is. As a shibboleth (or marker for group inclusion), the existence of this expressivity selects for programmers who want to fix rare, niche, unanticipated or disagreed upon things, and these people can be even harder "cats to herd".

Nim has other very flexible features - such as the rare "user-defined operators" that enable very nice looking custom syntax. But you can also SCOPE their use very simply. E.g., put their definitions inside a template so that you can say `ptrArithmeticNow: expression` and only have pointer arithmetic within `expression` (maybe with +! to look a little diff from ordinary arithmetic), and (ptrArithmetic itself can be scoped within a module name). It also has even more powerful (and more rare) term-rewriting macros, used most commonly in Lisp systems, perhaps doing computer algebra.

cb321··on Writing simple tab-completions for Bash and Zsh
It sounds like you don't know how the Bash/Zsh ideas I mentioned work. They run the command with --help, parse the output, and from that generate the completions, wiring them in to the completion system. That method is a zero config solution (well, you might need a list of such "command-names only" - no option names, which could change at any time -- so maybe "minimal config"). The OP & you mention a much heavier config solution which strikes me as against the vibe of Fish in general which is, supposedly, out-of-the-box niceness.
cb321··on Writing simple tab-completions for Bash and Zsh
For those programs that have betrayed shipping man pages, instead say relying only on a --help system, do you happen to know if the fish shell has an analogue to Zsh `_gnu_generic` and Bash `complete -F _longopt`? If not, do you have any insight into why not/what it would take to make that happen?
cb321··on Writing simple tab-completions for Bash and Zsh
The answer to your question is that command-lines have a much larger diversity of syntax (even to get help!) than most people realize. Folks have their 30..60 commands they run frequently and don't run into many or conveniently forget/neglect older ones like `gcc` or `tar` or `dd`. Many people (not saying you specifically) do not even realize that double-dash long options are a GNU extension never standardized or that Python toolkits typically allow --my-opt for --my-option abbreviations, just to name a couple of the dozen variations (space or '=', or ':' or '/' or any of the above or etc., etc.). There are probably hundreds if not thousands of syntax possibilities, but people often act like there is only one.

As an example of diversity estimation that you can try at home, a couple of times I have run every single command in my command search PATH with --help </dev/null >/tmp/help.$c 2>&1 . Caution - be careful if you do this! Have backups/checksums of everything important and run as an unprivileged user. I always have to kill off several processes that just hang doing something or otherwise manually intervene. Anyway, this alone suggests data collection of help text is not a trivial problem.

Beyond data collection, many commands did not/do not use CLI toolkits at all. Their commands may have even less regular syntax. Freeform help makes it harder to produce a regular help syntax to convert into the interpreter needed by a completion system. That said, as elsethread commented for some toolkits the Zsh _gnu_generic works great! It essentially IS the "automagic" system you might want, just for a highly restricted circumstance.

Any CLI toolkit itself does have the data, by necessity. So, if the CLI framework supports the 2 or 3 common shells there is no need for a translator exactly. You just need a code generator. There is a stab at an auto-generation framework from said data for the Nim CLI toolkit, cligen, over at:

https://github.com/c-blake/cligen/blob/master/util/complgen....

but it only works for Zsh right now. Anyway, I don't think perfect should be the enemy of the good or anything like that, but you seemed to ask an earnest "why" question and these are some of the complexities.

cb321··on Writing simple tab-completions for Bash and Zsh
_gnu_generic is fantastic. I use it all the time. If your CLI toolkit emits colorized help, but skips said colorization if NO_COLOR[1] is set then you can prefix _gnu_generic with NO_COLOR=1 as in https://github.com/c-blake/cligen/wiki/Zsh-completion-for-cl... for the cligen Nim CLI toolkit.

A similar thing in Bash is `complete -F _longopt YourCmd`, but these will not work with "multi-commands" that have "sub-commands" as the article of this thread covers. Truth is, as non-standard as GNU long opts already are, how subcommands work is even more non-standard (are there even global options? Or between each subcommand? Is it how Python does it? Or Go? Or some one specific library or ..?)

[^1]: https://no-color.org/

cb321··on AI must RTFM: Why tech writers are becoming context curators
There are anthropomorphic euphemisms like "hallucination", but isn't it true that LLMs literally Randomize TFM?
cb321··on Cligen: A Native API-Inferred Command-Line Interface Generator for Nim
There hasn't been a ton of benchmark comparisons. From memory, on what remains in examples/:

The cligen/dents stuff is faster than anything not so enabled with a new kernel call and some perf stuff mentioned right in the comments there, but Linux has moved to blocking installing syscalls from modules.

I've timed gl/grls against ripgrep favorably, but it's obviously a very simple pattern subcase. Mostly those were to demo cligen/procpool.

examples/cols was faster than awk or xsv for me (which has really moved to https://github.com/c-blake/bu now along with several others like `rp` - which is kind of a "concept piece" showing that any command-line is kind of a "language" already and with just a little codegen magic you can beat awk at most of its own games: https://github.com/c-blake/bu/blob/main/doc/rp.md).

Some subthread in one of the hundreds of closed issue threads got into a comparison of dups and jdupes ( https://github.com/c-blake/cligen/issues/99#issuecomment-485... ) where Jody shows up. I mean, in my test cases dups fared well, but on what many would call the "boring non-IO bound case". For IO bound cases things rapidly depend a lot on your host OS & devices.

I should say that "the point" of most of these things was not caffeine-fueled rage optimization, but more to show how easy it is to get performance & functionality out of Nim. Almost all are much smaller programs (perhaps with many fewer features) than their competing programs. But such catering to The Unix Philosophy felt like it fit well with a CLI toolkit. { Even sys_batch is like a 30 lines of C virtual machine for inside Linux instead of untold 10s of thousands for eBPF & IO uring. While porting to other arches would be more, that 30 lines could probably stay the same. }

cb321··on Writing Your Own Simple Tab-Completions for Bash and Zsh
For simple commands, it is pretty easy if your CLI toolkit sticks to a _gnu_generic kind of format, but for subcommands it gets hard.

Since in cligen (https://news.ycombinator.com/item?id=44820383) the "proc signature is already The Spec", it actually provides a multi-command/subcommand completion script generator in util/complgen.nim .

Bash/Fish logic would be nice if anyone wants to contribute after reading this article about it. :-)

← PreviousPage 4 of 29Next →