Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Sorry, I do not know any of these languages but I have used Haskell's STM which is incredible. Any of the languages mentioned here close to the STM idea (basically using strong types to ensure mutations happen in isolated context)?



The idea of STM has nothing todo with strong types. You can have STM with types or without.

Clojure has a full STM since version before version 1.0. Simple example:

(def stm (refs {}))

(alter! stm assoc :testkey "testvalue")

(println @stm)

See: https://clojure.org/reference/refs


It definitely has a lot to do with types in Haskell though. You can only operate on transactional variables in STM context. The STM action when run is what gives you atomicity and isolation.

Ref page 8-9 in [1]

[1] https://www.microsoft.com/en-us/research/publication/beautif...


Important part of STM is that the retry mechanism requires functions to be pure - which haskell compiler will check for you.


In fairness to clojure, you can label your side-effectey functions with `io!`, and it then won't let you use it in an STM retry function.

https://clojuredocs.org/clojure.core/io!


Technically, STM requires that you're not updating your state via side effects. That's a different proposition from requiring your functions to be pure. For example, you could have a function with a print statement inside your STM transaction just fine.


If you want to go way down that rabbit hole, it's an active topic of conversation in the Haskell community right now how to break up IO into more granular pieces than what the IO type natively provides, where a function is either "pure" or it's a dirty rotten effects producer, and no middle ground in between.

Although, even a stray print inside an STM transaction could actually do you a lot of damage, since "print" is actually a fairly expensive operation. I've written all sorts of programs in my life that were technically bottlenecked not on reading the input, writing the output, or any of the processing in-between, but on the printing it was doing. And, relatedly, every community that tries to write a really blazingly fast web server in their language runs into a bit of a wall around the mere act of logging the hits. (Even just the date computation starts to bottleneck things, but the writing does too.)


That's the difference between Clojure and Haskell mindsets in a nutshell. Clojure approach is to have sane defaults and guide the programmer towards doing the right thing, but ultimately letting them do what they need to. Whether it makes sense to do something or not is context dependent in practice. Ultimately, the person writing the code understands their situation best, and the language shouldn't get in the way of them doing what they need to.

You could of course argue that by preventing the user from doing certain things you avoid some classes of errors. However, I will in turn argue that by forcing the user to write code for the benefit of the type checker often results in convoluted solutions that are hard to understand and maintain. So, you just end up trading one set of problems for another.


> You could of course argue that by preventing the user from doing certain things you avoid some classes of errors. However, I will in turn argue that by forcing the user to write code for the benefit of the type checker often results in convoluted solutions that are hard to understand and maintain. So, you just end up trading one set of problems for another.

Funny, this is exactly the opposite of my take from a type system like Haskell's. The thing is, whether it's enforced by types or not, the same invariants exist in your code. The only difference is that in one case they are checked explicitly at compile time, and the other case they are hidden and can blow up your programs.

If anything, explicit invariants make code easier to maintain and understand.


Except that they're not the same. Static typing restricts you to a set of statements that can be verified by the type checker. This is a subset of all valid statements, otherwise you could just type check code in any language at compile time.

Static typing makes many dynamic patterns either difficult or impossible to use. For example, Ring middleware becomes pretty much impossible in a static language https://github.com/ring-clojure/ring/wiki/Middleware-Pattern...

The pattern here is that the request and response are expressed as maps. The request map is passed through a set of middleware functions, and each one can modify this map in some way.

A dynamic language makes it possible to write middleware libraries that know absolutely nothing about each other, and compose seamlessly. A static language would require you to provide a full description of every possible permutation of the request map, and every library would have to conform to it. This creates coupling because any time a library needs to create a new key that only it cares about, the global spec needs to be modified to support it.

My experience is that immutability plays a far bigger role than types in addressing the problem of maintainability. Immutability as the default makes it natural to structure applications using independent components. This indirectly helps with the problem of tracking types in large applications as well. You don't need to track types across your entire application, and you're able to do local reasoning within the scope of each component. Meanwhile, you make bigger components by composing smaller ones together, and you only need to know the types at the level of composition which is the public API for the components.


I understand that, but that has nothing to do with STM in general and everything with Haskell solution in particular.


Pony’s does this with the added benefit that all the checks happen at compile time. I’m only somewhat familiar with STM and think some of the safety checks are at runtime but could be wrong.


One of the issues with STM is you can't use IO inside the atomic blocks, which is easy enough to get around but if your users are unaware it'll present bugs. Haskell enforces a separation of IO at the type-level, hence this risk is gone so you get every benefit of STM with none of the risk. STM itself eliminates a set of concurrency problems based on the way it works, but you do still have to do some run-time checks to eliminate deadlocks (i.e. use `modify` functions or have the atomic blocks behave like them, or you could end up with a deadlock).


The safety checks are at compile-time, since it only allows pure functions to be run. You can circumvent this of course with unsafePerformIO, but then you are completely on your own.


Rust uses types to ensure mutations happen in the correct way as to prevent data races (compile time checks)




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: