RU version is available. Content is displayed in original English for accuracy.
Advertisement
Advertisement
⚡ Community Insights
Discussion Sentiment
70% Positive
Analyzed from 3198 words in the discussion.
Trending Topics
#zig#type#language#don#languages#write#same#using#more#rust

Discussion (75 Comments)Read Original on HackerNews
But, flipping the script, if you want to see something like Zig's `Io` interface in Haskell then have a look at my capability system Bluefin, particularly Bluefin.IO. The equivalent of Zig's `Io` is called `IOE` and you can't do IO without it!
https://hackage-content.haskell.org/package/bluefin-0.5.1.0/...
Regarding custom allocators and such, well, that could fit into the same pattern, in principle, since capabilities/regions/lifetimes are pretty much the same pattern. I don't know how one would plug that into Haskell's RTS.
[1] Languages designed around capability passing often have other features, like capture checking to ensure capabilities aren't used outside the scope where they are active. There are only two such languages I know of. Effekt (see https://effekt-lang.org/tour/captures) and Scala 3 (see https://docs.scala-lang.org/scala3/reference/experimental/cc...) However, this is not core to the idea of capability passing.
https://i.imgflip.com/65gu3j.jpg
I don't see how it's true in any meaningful sense. It seems about the same as stating that any function is an example of the reader monad.
The whole point of monads in programming languages is as an _abstraction_ that allows one to ignore internals like how the IO token is passed around.
Maybe Zig is a language for people who are scared of abstraction. Otherwise they'd presumably be using something more powerful like Rust.
1. Go, when I first saw code I wrote almost a decade ago still compiles and runs in Go, I decided to use Go for everything. There were some initial troubles when I started using it a decade ago, but now it's painless.
2. Haskell, I use it for DSL and state machines.
3. Bash for all deployment scripts and everything.
4. TypeScript, well for the frontend.
Lately, I’ve been using Go and SQLite for nearly everything.
I don't think I’ve any motivation to look at any other language.
I gave up on Java, Python, Ruby, Rust, C++, and C# long ago.
Fun fact:
Same thing for cloud, I just don't use managed cloud services anymore. I only use VMs or dedicated servers. I've found when you want to run a service for decades+, you’ve got to run your own service if you want it not to cost a lot in the long run.
I manage a few MongoDB, PostgreSQL clusters. Most of the apps like email lists marketer (for marketing, sending thousands of email each day) are simple Go app + SQLite using less than 512MB RAM.
Same for SaaS billing, the solution is entirely written in Go and uses Postgres. (I didn’t feel safe here using SQLite for this for a multi-tenant setup.)
Our chat/ticketing system is SQLite + Go. Deployment is easy, just upload Go cross-compiled binary + systemd service file, alloy picks up log and drops it graphana which has all alerts there.
I don't need to worry about "speed" for anything I do in Go, unlike Ruby/Python.
When something has to be correct I define it model it in Haskell as its rich type system helps you write correct code. Though setup is not painless as Go, decent performance.
I write good documentation, deployment instructions right into mono repo. For a small team this is more than enough imho.
No Docker, no Kubernetes, just using simple scripts + graphana + prometheus + Loki and for alloy/nodeexporter. Life couldn't be any simpler than this.
Despite we have different tastes in language and are in completely different ecosystems, TypeScript is still the lingua franca lol.
Shell for the scripts. I haven't tried to work through much DSL as I really am not a fan of DSLs. Maybe I'll give haskell a shot again to see if it sticks.
Especially regarding Bash.
Used to be in a few companies where most developers just couldn’t/wouldn’t write in more than one language and it was always a pain to maintain different runtimes, languages, packages and internal dependencies of things that could have been a 20-line bash script, and had to be maintained and updated from time to time.
I understand people have their own limitations and reasons, but having to constantly deal with “wrong tool for the job” for the thousandth time gets frustrating.
Especially in cases where four different languages were used across the company because different people had different preferences. Worst case was Python/Ruby/C#/Javascript.
I get that Bash is not perfect, but I enjoy the simplicity and directness, and dislike the multitude of problems caused by not using it have shown to me it’s a better tradeoff.
Curious if you've tried to use agents to read / write Haskell and how the experience has been?
Dabbled with Rust some years ago, I think it is an excellent choice for sudo-rs and such but for GUI and web apps I (perhaps too stupid) end up with arcmutex soup.
https://www.radicalsimpli.city
If trying to avoid the cloud, like OP, which hosting option is suitable for Clojure, what do you use? I believe Clojure (JVM) has higher RAM requirements?
And GO has pocketbase.io which looks quite interesting. Do know whether something similar exists for Clojure, or maybe it's straightforward enough to compose your own by using various Clojure libs?
We’ve a 1: 1 copy of the app; on JVM, it's using 2GB RAM using Spring Boot, and on Go, it runs on 512MB RAM and is blazingly fast.
ofc, it's possible to tune java app but why bother? when we get same low resource usage and better performance in Go from get go while still writing naive and dumb code?
Deployment is super simple in Go, upload a single cross compiled binary it's done. Very simple and easy.
Rust needs a lot more effort to write correct code than Go in my experience. We get the same performance out of Go, with much less effort. At some point, it's just cheaper to start one extra instance than perform some low-level optimisation; modern hardware is fast enough that Rust-level optimisation is rarely needed for what we do.
If I use a JVM language, running my test suite takes 10 to 30 seconds. With Rust it spends 3 seconds compiling and half a second to run 250 tests.
The irritating parts of Rust are more related with bloated libraries like serde that insist on generating code which massively slows down compilation for not much benefit.
In my opinion, the concept of automaton is fundamental and it deserves equal standing with the concept of function (even if it is a higher level concept that is built upon that of function).
I believe that functional programming is preferable wherever it is naturally applicable, and most programs have components of this kind, but most complete application programs, i.e. which do input and output actions, are automata, not functions and it is better to not attempt to masquerade this with tricks that provide no benefits.
Therefore, I prefer a programming language that has a pure functional subset, allowing the use of that subset where desirable, but which also has standard imperative features (e.g. assignment), to be used where appropriate.
It's dependency injection. and yes, you can model dependecies like a monad but most people, even in less pure fp langs, don't.
i don't really say this to just be a pedant, but if you're an fp enjoyer, you will be disappointed if you get the picture that zig is fp-like, outside of a few squint-and-it-looks-like things
And he does admit you may have to squint, to appreciate the fp capabilities provided by Zig.
For example Swift enums, while in some ways clunky, can do a decent job both as newtypes and as sum types (unlike Java enums, which are a fixed collection of instances of the same class).
here. i am not the only one that refers to it as dependency injection:
https://daily.dev/blog/zig-async-io-io-uring-zig-0-16-rethin...
"Zig 0.16 introduces std.Io, a flexible I/O abstraction that uses dependency injection, similar to the Allocator interface"
the annoyingness of the thing you tried to do in zig is a feature. its a "don't do this, you will confuse the reader" signal. as for optional, its a pattern that is so common that it's worth having builtin optimizations, for example @sizeOf(*T) == @sizeOf(usize) but @sizeOf(?*T) != @sizeOf(?usize). if optional were a general sum type you wouldn't be able to make these optimizations easily without extra information
In Rust, which is arguably also a low level language, it looks like this:
In Zig, that means being able to use the language itself to express type level computations. Instead of Rust’s an angle brackets and trait constraints and derive syntax. Or C++ templates.
Sure, it won’t beat a language with sugar for the exact thing you’re doing, but the whole point is that you’re a layer below the sugar and can do more.
Option<T> is trivial. But Tuple<N>? Parameterizing a struct by layout, AoS vs SoA? Compile time state machines? Parser generators? Serialization? These are likely where Zig would shine compared to the others.
I don't mind escape hatches - as long as they're visible/greppable in the source code. You can always write undefined/error/panic/trace directives while you're coding, then come back and remove them later.
This feels like the direction Algebraic Effects might take us.
If you write this as a monad, your get very similar syntax to procedural code.
An exception is different to an Either result type. Exceptions short circuit execution and walk up the call tree to the nearest handler. They also have very different optimization in practice (eg in C++)
I opened the network log, disabled cache and reloaded to see it only transferred 8kb.
Keep up the good work!
I don't think this even qualifies as correlation.
> ...
> What facilities does the language provide me to create correct-by-construction systems and how easily can I program the type-system.
Isn't programming the type-system orthogonal to the program's domain in the same way that manual memory management is?
Can comptime blow up compile times? Does it have arbitrary cutoffs like C++ template depth?
In which case, what's the term for the "proper sum types and pattern matching" flavour of things?
- most of them are dynamically typed (thus don't need sum types, as there are no types). The ones that do have gradual type systems likely either implement some form of them (off the top of my head I can only remember typed racket, and I think it implements them through union types)
- not all lisps lean functional: I believe that's mostly a prerogative of scheme and clojure (and their descendants); something like CL is a lot more procedural, iirc
- in most lisps, thanks to macros, you probably don't need the language to support some sort of match construct out of the box: just implement it as a macro [1]
In general the "proper sum types" side of functional programming is just the statically typed one, but even in dynamically typed FP languages you end up adopting sum type-esque patterns, like elixir's error handling (which closely resembles the usual Either/Result type, just built out of tuples and atoms rather than a predefined type), and I assume many lisps adopt similar patterns as well
[1] https://github.com/clojure/core.match
I actually ship stuff in Haskell believe it or not. I also think Zig is very cool and have played around with it quite a bit. Yes, garbage collection hurts performance, but the reality is that the overwhelming majority of all software does not suffer from the performance loss between well written code in a reasonably performant functional gc language and a highly performant language with manual memory management. It’s just not important. But not having to deal with the cognitive overhead of managing memory and being able to deal in domain specific abstractions only is a massive win for developer productivity and code base simplicity and correctness.
I think OxCamls approach of opting in to more direct control of performance is interesting. I also think it’s great that many functional patterns are making their way into imperative first languages. Language selection is always about trades offs for your specific use case. My team writes Haskell instead of Rust because Haskell is plenty fast for our use case and we don’t have to write lifetime annotations everywhere and think about borrowing. If we needed more performance we would have no choice but to explore other languages and sacrifice some developer experience and productivity, that’s very reasonable. I’m also not saying performance doesn’t matter (if you’re writing for loops in Python, stop). But this read to me like “because better performance exits with manual memory management, all garbage collectors are bad, so I’ll force zig to be something it’s not in order to gain performance I probably don’t need”. Which to me is an odd take. A more measured way of thinking about this might be, it can be useful to leverage functional patterns where appropriate in low level languages, if you find yourself needing to write code in one.
I would take another look at Common Lisp if I were the author. Manual memory management is very much an option where you need it.
can you elaborate? theres only what 11 datatypes in elixir?
[a: 1, b: 2] == [{:a, 1}, {:b, 2}]
Or maybe atom vs string keys in maps?
%{a: 1} vs %{"b" => 1}
Or keyword lists always needing to come last in lists?
[some: :value, :another] # error
[:another, some: :value] # valid
Or maybe something else entirely. Those are just things I remember having to lookup repeatedly when I was first learning elixir.
Honestly this sounds like monad bullshit. That's a struct/class/ADT/whatever you want to call it, they existed since forever. The only idea Zig had was that maybe we shouldn't make them global instances.
In addition to the normal value to value, type to type, and type to value functions, in comptime, you can write static value to type functions.
In full dependent type, you can in addition write dynamic value to type functions, completing the value to type corner.
So in terms of typing strength, plain Haskell < Zig < dependent type languages.
I haven't heard anyone writing code in Elixir complain about performance issues.
btw we do sometimes bitch about performance :)
Why write:
EqPoint.eql(a, c)
When you can write:
Point.eql(a, c)