To elaborate, CML is better than golang's channels and isn't broken (requiring mutexes over channels to avoid breaking qualifies as broken). SML syntax is simpler to learn, but still offers much more powerful abstractions with pattern matching. SML uses options instead of null pointer exceptions. Hindley-Milner types are clearly superior to Golang's type system (especially things like empty interface). SML has had generics for almost 25 years now while go languishes. SML has immutable data structures and they are the default for almost everything. Golang has implicit types, but SML's implicit types are much better -- especially structurally-typed records. SML's modules are amazing and simply have no comparison in go. Finally, SuccessorML is doing work toward adding a few new features to the language (for example, module type classes), but the language has an actual specification that anyone can implement (and many have) rather than the implementation effectively being the spec.
As for why Go isn't SML, just look at the original team. They had a background in languages like C (Ken Thompson , Oberon and Modula (Robert Griesemer) and Aleph, Newsqueak, and Limbo (Rob Pike). Nobody on the core Go team have ever been fans of functional languages or of complex type systems.
It could also use some newer things like better support for unicode strings.
[0]: http://sigbovik.org/2019/proceedings.pdf
[1]: Page 170, Also here: https://github.com/solb/sigbovik/blob/master/papers/SIGBOVIK...
Especially cruel is the "andalso" in the closing sentence.
https://github.com/aliceml/aliceml-alice
and make sure you have all the dependencies installed first, it does build on linux and its fun to play with.
I am currently working on a AliceML derived language and if there is any interest I'll post a note on HN - once its a bit futher down the line.
I've used Go channels plenty of times without any explicit mutexes, so I'm not sure what you're talking about here.
I'd add that these aren't just theoretical (EDIT: I forgot that his article linked to this paper).
https://songlh.github.io/paper/go-study.pdf
Go doesn't actually implement CSP correctly. That would require some changes. This provides a high-level overview.
https://stackoverflow.com/questions/32651557/golang-main-dif...
Threading is hard and gets a lot harder when you introduce pointers or mutable data. This is even harder when your type system is terrible. I suspect that Go trying to solve this would result in a language that looked a lot like Rust with a Garbage Collector.
You aren't doing your argument any favors when you're so hyperbolic. Go channels are manifestly not "broken."
> I suspect that Go trying to solve this would result in a language that looked a lot like Rust with a Garbage Collector.
And yet you honestly can't understand why the designers of Go didn't go down that path? Rust is significantly more complex than Go and much harder to learn.
Which of those features have they said the don't want?
https://tour.golang.org/concurrency/1
https://saityi.github.io/sml-tour/tour/03-01-spawn.html
There are differences, but once you get to that point in either tour, the differences shouldn't make it hard to read; if it is hard to read or understand, that would be welcome feedback, as I have failed in my attempt to present Standard ML well. :)
(I originally posted this as a 'Show HN', hoping for feedback, so it is totally welcome.)
I'm not sure I understand why this is true for Go rather than a statically typed FP language. If anything, I'd consider languages like Haskell or ML to be better at this.
- The obvious case is handling `nil`. In Haskell, you'd treat this as `Maybe a`, and it's obvious that you're not handling the nil case since the pattern match is incomplete. In Go, you get a nil pointer exception at runtime. You could check for the nil case for every pointer, but that's too onerous, so now you're left with relying on the programmer on checking
- Go doesn't have exceptions, which removes some of the hiding of bad code (see https://devblogs.microsoft.com/oldnewthing/20050114-00/?p=36...), but you can still accidentally use a result when an error is return (in fact, you have to return something!). In Haskell, you're forced to pattern match on an `Either`, which again exposes bad through an incomplete pattern match.
I don't think these are arguments for FP per se, but I do think that statically typed FP languages (and the languages they inspire) are much better about removing footguns and making bad code hard to write, simply because the typechecker is so restrictive. As a more general principle, instead of making bad code screamingly obvious, make it impossible to write!
Now, you mention later in the thread that you've already used Haskell before, so you're probably aware of all this and I may misinterpreted what you meant. Did you mean a different form of bad code?
I spent a long time learning Haskell and have written enough Clojure especially to safely call myself an expert in it. I obsess over languages (not proud of that btw). FP is hard. It's too hard for most programmers. A lot of people avoid saying this but it's true. It's hard in a way that you can't fix with experience.
That's why the tooling for it sucks. Suppose you want to make an IDE or even an API for your product. Do you want a lot of people to use it? Then you use ${BLUB}. Heavily abstract stuff dies. Look at Tensorflow vs. Pytorch. Tensorflow 1.0 had this wonderful functional, declarative, immutable way of writing neural networks, but people protested because they couldn't mutate variables in a for loop and have it automagically autodifferentiate. Abstraction kills network effects.
> every new language except Go has incorporated significant functional features
None of them come close to the semantics of FP (except Scala, but its negative adoption rate kind of proves my point). They all allow side effects which breaks the first rule.
You can say they stole some of the convenient syntactic sugar from FP, like pattern matching and lambdas (e.g., Kotlin and Rust). Great. So what advantage do FP langs have now? Making it hard to do I/O? Category theory? Having unmaintained compilers?
In any case, let's look at those haskell "hard parts" and contrast them.
Haskell treats everything as immutable. SML is NOT always immutable. It simply adds some (very sane) constraints on where mutation can occur which makes reasoning about mutation easier.
Haskell forbids side effects. This is an illusion of course (you'll find high-performance libraries often use the unsafe IO stuff). SML embraces side effects though general practice is to restrict them to specific modules to make the program easier to reason about (a good practice in pretty much all languages). You can definitely write imperative pieces of code if it improves performance.
Haskell is lazily evaluated. This can be a performance boon for some things, but is a bane to find and fixing performance bottlenecks. SML is eagerly evaluated like most other languages.
Haskell is crazy for typeclasses. They use them everywhere, but they often complicate actually using things. SML does not have type classes, but SucessorML (the next version) is looking at adding module typeclasses instead. These will have most of the same features, but because they are in the module instead, it should put a big limit on their abuse and make their use easier to track.
SML's big "ivory tower" ideas are: powerful type system, pattern matching, option types everywhere, first-class functions, and modules (essentially as a replacement for classes though that is not entirely true).
SML really is the whole package. The best parts of functional programming combined with the best parts of mainstream languages (restricted to the "good" subset) all tied up in a language definition that manages to reduce the amount of syntax compared to something like Java or Go while increasing the amount of power while at the same time not turning into an unreadable mess (it's pretty hard to write unreadable SML code).
Here's a pretty thorough comparison of the two http://adam.chlipala.net/mlcomp/
That said, I love how small it is, too. Every time I go back to SML, I find it refreshing how much power there is in a few simple ideas expressed well.
but well I was acquainted to typed FP before, to some or the mere mortals (just kidding) it might look like a sad joke.
There's also a famously concise intro to SML (in 22 pages!) by Mads Tofte:
https://condor.depaul.edu/dmumaugh/readings/handouts/CSC447/...
Concurrent ML is definitely worth looking at.
It is a really nice system that is similar to go, but the primitives it gives you are in a way a generalisation of go's concurrency primitives.
https://discuss.ocaml.org/t/multicore-ocaml-january-2020-upd...
SML is kind of like the lisp of FP. It's just the minimal core of parametric polymorphism and hindley-milner type inference, so as a beginner you don't spend thought cycles language-specific features (that would make it more useful as a general purpose language) - you just learn and understand the core concepts and techniques (such as building intuition for how to replace different kinds of loops with recursion and pattern matching) that will apply to all FP/HM languages.
Lisp is the Lisp of FP.
SML is more the SML of FP. Alternatively, SML can be viewed as the Lisp of statically-typed FP, in that, loosely, SML : Static FP :: Lisp : DynamicFP
Notably, the paper that (I believe) coined the term, "Can Programming be Liberated from the Von Neumann Style?" by John Backus, spent a fair amount of ink on specifically calling lisp out, and contrasting it from what he had in mind. IIRC, he felt that FP was preferable to the lisp model because lisp gives you too much power: Mutable variables, ways of composing functions that are potentially more error prone due to domain/range mismatch gotchas, stuff like that could all be impediments to ensuring the correctness of programs. I think that, to some extent, a sort of Haskellian fastidiousness about correctness is what he felt this proposed functional style of programming was all about.
To me it feels much more like a symbolic computation oriented language.
But maybe it is because I associate too much fp with some kind of parametric typing
What I would like is a language as simple as SML in the JVM. Kotlin was the closest thing I found with good adoption.
Out of curiosity, what is lisp the lisp of? (It's not lambda calculus, for that is the lisp of lisp.)
You don't consider lisp the lisp of FP?
Haskell is the poster child, but quite rich in syntax and concepts. So while I wouldn't really agree with grandparent, it's at least defensible to see Standard ML as a small core, a kernel of functional programming languages.
I generally test with MLton and Poly/ML, and MLton does not always produce faster executables, though most of the time it does. Poly/ML is a good compiler as well, and it has much faster compile times.
To fix it, I will rewrite this section to say that the standard requires optimising /tail-recursive/ calls away, and modify the example to be iteratively recursive, but I'd still like to assuage fears of recursion in the tour. The 'while' loop in SML is actually just a derived form, and is rewritten to recursion; there is no looping in the language. Do you have any suggestions for rewording?
This is a work in progress that I was looking for feedback on :) Thank you! If you see anything else fishy, please let me know.
One practical nit is that the position of the "forward" button moves around depending on how long the section title is, so you have to keep chasing it. Possibly it could move to the left of the section title, so back and forward buttons are clustered together in a fixed place?
If you find anything else, issues and feedback are also welcome on the issue tracking @ https://github.com/Saityi/a-tour-of-standard-ml/issues -- I'm primarily a backend engineer, and this is my first foray into frontend; even nitpicks like this are very welcome. :)
I had the same problem getting used to Nim with it's Pascal inspired syntax. But being different is rarely a bad thing as long as it works.
Yup, that family of syntax descends from Standard ML.