You can find monads everywhere if you look hard enough. But Haskell actually lets you use those discoveries for massive code reuse, while Rust forces you to write stuff like Result::and_then and Result::map over and over again.
> To say that Rust is a child of Haskell is really overstating the case.
Right, most of these are common influences. I'd say Rust's main contributions are (a) the ownership system, and (b) painting a thin coat of C++ syntax over ML features to make them more palatable, and neither of these seem all that inspired by Haskell.
(Of course you still want some form of nicer notation than the raw one, but that's beside the point.)
Out of the remaining monads that are in common use (Result, Option, Future), it doesn't seem particularly worthwhile to try cram them all into the same API, when you could instead try to design better specialized APIs (as has been done with Future)
In practice applicative functors are even more prevalent than their subset, monads.
If you can express your algorithm using the weaker applicative combinators only, there's more opportunity for optimisation and parallelism. (And the code is easier to understand for your readers, because there are fewer interactions for them to worry about, too.)
I just implemented the Haxl paper in Java as a weekend project (for implementing GraphQL APIs), so it totally does ;)
I think you're being a bit short-sighted here.
I fully appreciate that it might be extremely difficult to accommodate these HKTs, etc. in a language with the core values of Rust. That's absolutely fine, because that's probably several research-level projects in and of itself. OTOH, we shouldn't discount the value of such things.
It's absolutely fine and reasonable to choose e.g. efficiency over other things, but it's not the only value.
Little abbreviations might be widely used, but when people talk about code reuse I'm hoping to hear about ways to share larger chunks of code, not having shorter idioms.
In Rust, the functions Result::and_then, Result::map, Option::and_then, Option::map, etc. are pretty much all saying the exact same thing, but they need to be reimplemented over and over again. With Haskell, Result and Option (and [], QuickCheck.Gen, Data.Binary.Get, etc.) just need to implement return and (>>=), and they get a ridiculous amount of useful functions for free (mapM_, foldM_, forever, ap, etc.). That's a "large chunk of code" saved in my opinion.
https://hackage.haskell.org/package/base-4.14.0.0/docs/Contr...
And, of course, they also get some very nice sugar in the form of do notation.
Having popular interfaces like monad, applicative functors, monoids etc also suggests common ways to structure any API you are working on.
If you design an API from scratch and have to implement everything again and again, you might be tempted into a pretty arbitrary breakdown of the problem space.
Whereas in Haskell personal laziness drives you to look hard to see if you can express your API in terms of foldable and traversable or monads etc so that you get lots of functions for free.
Those common interfaces also automatically suggest gaps in your API that otherwise you would have only noticed after using your API for a while (or by being a very experienced programmer).
The feature of Haskell that lets you avoid writing 'and_then" over and over is do-notation.
Beyond that, it's not clear that a monad trait would work, even if we did. It may, but it also may not. We'll see (or not) I guess.
You mean you couldn't express it in Rust, even with HKTs? Or you mean that it wouldn't be useful?
There have been some people working on these problems, take https://varkor.github.io/blog/2018/11/10/monadic-do-notation... for example. This is why I say "not clear" and not "impossible" or something. We just don't know.
Rust does not have higher kinded types, which are required to have abstractions like the Functor and Monad type classes in Haskell.
Option and Result are monadic, but they must implement their own, separate methods and can not share a common interface. Which takes away a lot of expressive power.
There is a accepted proposal for something similar, known as GAT [1] (generic associated types). But that is blocked on other work and might never actually be implemented.
In a theoretical sense, sure, but in a practical sense, my understanding is that GATs already exist in chalk, and so it's still a matter of putting in the work, not a question of "is this possible or not."
You would know better of course, but it was my impression that there is no firm commitment to stabilizing GAT, even once the compiler fully switches to Chalk, due the potential disruptiveness and complication of the language.
(For those unaware: Chalk is a rewrite of the trait logic in rustc, based on logic programming [1] )
Certainly, but not in the sense of being uncertain about whether or not GAT are worthwhile, only in the sense of not wanting to make promises that may not be able to be kept. The Rust developers want GAT--or something analogous to GAT--and much work has been put in towards that goal (work that will have been useful even without GAT), and at this juncture there's no foreseen theoretical impediments; however, that's not to say that some unforeseen impediment won't yet arise. Such a thing happened with specialization, which has been in the works for much longer (current status: https://github.com/rust-lang/rust/pull/68970), and the lesson has been to not incite expectation until you are extremely certain the promise will be able to be kept. As a mere half-informed bystander it seems that GAT not encountering the problems that specialization did, but don't take this as any sort of promise. :)
Rust is already a complicated language. Those two features would increase the complexity of the type system quite significantly.
Possibly beyond what is healthy for Rust adoption.
I think async trait methods could be stabilized without exposing GAT to the user.
Of course I would very, very much like to have them either way, they would make certain abstractions much easier to build.
With the caveat that some languages support an encoding of HKTs. You can encode HKTs in OCaml, Kotlin, or TypeScript for example, and there are FP libraries taking advantage of that. I believe the encoding was inspired by this paper:
https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-ki...
It kind of sucks, but it does let you work with a Monad type class, and it's better than not doing it at all.
I'm not sure if such an encoding is possible in Rust. I've been told that F# has a hard time with it, not sure why (possibly because of reified generics). In TypeScript and Kotlin it works because the runtime and the type systems are relaxed and you can just force downcasts if you're in trouble.
Just to add some tangential color here: even if you don't intend to use this encoding in production, it can be a fruitful intermediate step in refactoring! I just recently ran into a situation in Java where I had some tightly-coupled concerns for which HKTs were the obvious way out. Since Java doesn't have HKTs, I applied the HKT encoding to get the hard part out of the way, and then discovered a simplification that brought me back into the realm of idiomatic Java. I would not have found the final solution had I not pathed through HKTs -- I actually thought it would be impossible to do this decoupling properly at first!
More color: the paper itself describes this approach as an application of defunctionalization. I use defunctionalization a lot, too, to write an obvious recursive solution and then mechanically transform it into an iterative one. So it's nice to know how to apply it at the type level, too ;)
Well, functions and lists are also monads. I guess what the author means is that the language recognises the similarity of those disparate structures.
The article really rubbed me the wrong way with this tidbit
> (we don’t talk about Go…)
So since the author is speaking of "premier system’s language" we choose to ignore the world's premier cloud language? Interesting.
EDIT: also, strictly speaking, Rust doesn't use Hindley-Milner, and we don't have parametric polymorphism.
struct Person<T> {
age: T,
}
Is parametric polymorphism and valid Rust.There's also more minor things like https://github.com/rust-lang/rfcs/pull/1210#issuecomment-179...
If you ask all the developers out there to describe parametric and ad-hoc polymorphism I think a vast majority would give the example of a type parameter (e.g., Java generics or C++ templates) for parametric polymorphism and Java interfaces or Haskell's classes for ad-hoc polymorphism. I can even quote directly from Pierce (Types and Programming Languages):
> Parametric polymorphism, the topic of this chapter, allows a single piece of code to be typed "generically," using variables in place of actual types, and then instantiated with particular types as needed. Parametric definitions are uniform: all of their instances behave the same.
I think Rust and the aforementioned languages fit this definition. Outside of a specific compiler issue, claiming otherwise seems to only confuse the issue, especially for those just casually reading and not familiar with programming language theory.
In practice it is often useful to drop our demands for rigour by a bit. But any reasonable definition of parametric polymorphism _has_ to exclude C++ templates.
C++ templates are much closer to the definition of ad-hoc polymorphism.
My practical less-than-rigorous rule-of-thumb for parametric polymorphism amounts to something like: whenever Wadler's Theorems for Free paper applies. https://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf
Theorems for Free means that eg the type of the identity function (forall a . a -> a) guarantees that even if you supply it a bool, it can't magically turn into an implementation for `not`, or multiply all integers by 2.
Which doesn't mean it's terrible to get it wrong, just want to be clear about what Rust does and does not have. It is important because these kinds of definitions either hold or they don't; "sorta kinda mostly parametric" isn't the way folks tend to think about this. Which makes sense, because they're interested in proofs and formal definitions.
Yes, Pierce is great! But the issue is:
> all of their instances behave the same.
This is not true in Rust, as shown in my comment and the other replies. We have accepted APIs that break this property, and we have other language features on the way that break this property.
fn foo<T>() -> usize {
std::mem::size_of::<T>()
}
foo::<u8>(); // 1
foo::<u16>(); // 2The _behavior_ of this function is the same for all types, the _output_ is different. That is, for all types, the function body is the same. Maybe there is a more abstract definition of parametric polymorphism you are using, but as I said above, this seems pedantic.
fn foo<T>() -> usize {
let x = std::mem::size_of::<T>();
if x % 2 == 0 {
panic!();
}
}
foo::<u8>(); // 1
foo::<u16>(); // panic
That the body is the same isn't necessarily the issue at hand (though of course it's still a useful property in its own right), what matters is that reasoning about what this function will do requires knowing which specific types it is used with.> this seems pedantic
The first code example is merely the simplest demonstration, in the wild I would expect lots of `size_of` in generic contexts to result in type-dependent behavior somehow.
I'm not saying this is necessarily a very bad thing, nor do I have strong opinions on the usefulness of strict parametricity (which AFAIK Haskell doesn't have either). But in discussions relevant to parametricity, it's useful to know the ways a given language can subvert it (and Rust will further encourage it to be subverted, once the specialization feature is developed).
In practice Haskell seems to have pretty strong views on enforcing parametric polymorphism, doesn't it?
Haskell gives you ad-hoc polymorphism via typeclasses and there are also existential types and GADTs etc, if you need those. But once you declare something to abide by parametric polymorphism, you are expected to keep your end of the bargain.
(Yes, you could violate the pact via some unsafePerformIO trickery, but that's considered bad form.)
If you functions routinely violate those limits as a matter of course, those guarantees are useless.
I'm all for abusing notation and terminology a bit when it makes sense in practice, but loosening our definitions too much risks making them useless, too.
In practice in Haskell, I often only need a helper function for eg integers, but when the implementation allows, I will give the function the most parametric-polymorphic type that fits, because that makes the readers job easier:
Just like an invocation of `filter` is easier to read than a for-loop, because `filter` is strictly less powerful than the loop.
(In addition, the more general type serves to give the compiler a hint, so it can yell at me in case I accidentally do an operation on the integer that I didn't mean to.)
> However, in 2020, the premier system’s language is surely Rust.
Yesterday I was speaking with a friend who runs a small company doing contracted embedded programming (the embedded OS is often Linux but often not) and he'd never even heard of Rust. Maybe it's a little lower level than what's meant by "systems" here but still, the article's statement is just delusional.
I can go to Green Hills and get a contract that guarantees their compiler will be available and supported for my target platform for the next 10+ years. Rust is still a moving target with a single implementation, and no (present) way to get the guarantees needed for the embedded domains I've been involved in. I'd like to see Rust experimented with there, but it won't be the "go to" language for quite some time.
Absolutely. I've said it before, I will say it again, Rust will not be taken seriously as a systems language until it is specified. As I understand it years ago I was laughed at for this, but it is underway. I am grateful for this.
I forgot to preface my statements with I Love Rust, or, moreso, I actually love OCaml, and I love OCaml inspired languages, and I would love to write an OS in it (save ReactOS).
That being said, the desire to label it as the "premier systems language" (and no fault of the author, many before have tried to say this) is entirely not grounded in any sort of reality.
Whether or not these advances make a difference (positive or negative) in production is different question, but I'd rather not use "how much is a language used", as an inherent quality metric for a programming language. Otherwise, I fear we might all end up doing old-school Java, Javascript, Visual Basic and ABAP.
Define quality. Because what I've realized with the PL-elitist crowd is this almost always boils down to "the ability to be expressive" which is fine but it certainly isn't a complete metric, and, to borrow a common refrain, we've had expressive languages in the 70's as well, we've had s-expressions for a while now. What about other metrics, such as "ability for mass amounts of programmers to program the computer to do the correct thing?" and "ability for programmer to maintain said program?" -- you know, real world quality. My point is, people's usual quality metrics are often incomplete or narrowly defined.
In either case, if Go is so primitive, then why didn't the 70s produce a language like Go? Why did developers who created languages, operating systems, and systems software in the 70s not make Go until the 2000s (you do know who created Go, right?)
I just don't buy this argument, especially having used those "advanced" type systems for many years, sorry.
But all that says is that the magic of Go isn't in the type system. The couldn't produce-it-until-the-2000s isn't in the type system. The thing that makes Go more used in the real world than Haskell isn't the sophistication of the type system.
I don't see much to argue with in those claims.
My second point was that the popularity of Go (or any language) does not mean that it is inherently a good language, where good can mean, productivity, defect rate, .... Adoption of a language depends on many things, I believe quality of the tools, availability of libraries, and good old marketing (the Google aura around Go has helped in adoption) are often more important. The effort to make an initial, crappy, proof-of-concept is in my experience often decisive for the choice of language. The cost of long term maintenance (where a good type system might bring value) is rarely considered.
For Go, it also hasn't hurt that some of the core developers where being paid by Google to work on it.
The metric of the "ability for mass amounts of programmers to program the computer", is indeed a very relevant one, and I think Go shines there. When reading Go code, I typically find it easy to understand what the goal of the code is. However, "to do the correct thing", is often less of a success: there are off-by-one error and corner cases that have bugs, and reimplementations of the same basic logic. If you have a lot of developers available, you simply have them fix these issues, and your Go project becomes a success.
My point is, that in this scenario, the language itself is of minor importance: success is determined by the fact that you have access to a large pool of relatively cheap labor of reasonable quality (Google or VC funded startups are perfect examples). A 20 or even 50% productivity gain by having a "better" language would simply not matter for the outcome, certainly not if it takes your labor longer to get up to speed.
So, in that context, compared to the alternatives, the quality of Go is relatively high. But I do think that, if during the design of Go they had taken a couple of basic concepts from ML like sum types, polymorphism, and a decent module system, they would have ended up with a language that would make the current Go look unproductive and poor quality, even in that same context. It wouldn't make a difference to Google, but it would make the life of many a developer a little bit more productive and joyful.
And yes, I do know who created Go, but arguments by authority carry little weight.
I have the opposite experience. At a micro-level you might be able to tell what a line of Go code is doing, like manipulating this variable into that state etc, but it's such a tedious language that drowns the reader in code that it can be very hard to see the forest for the trees.
As an example of a deliberately simple language that mostly easy to read, I would perhaps cite Erlang.
In any case, I agree wholeheartedly that Go would benefit from sum types. (Though given the weak type system otherwise, you couldn't even implement a useful `Maybe` in Go. You'd really want parametric polymorphism to make sum types shine.) A way to express immutability would also have been welcome, especially given that they pride themselves on concurrent programming.
The "PL-elitist" crowd looks at programming languages from a computer science perspective, while the "PL-pragmatist" crowd (for lack of a better term) looks at it from a workplace perspective. At least that's how I see it.
In other words, one group writes tooling and libraries; the other writes business applications using said tooling and libraries.
One paradigm invites complex, highly expressive code as to minimize the chance of unforeseen errors (even if just by having a smaller codebase), and as to provide a clean and powerful interface that "does more with less".
The other invites simple, highly maintainable code as to minimize the training overhead of new employees and the chance that their lack of rigor might end up accidentally introducing or reintroducing bugs. Furthermore, a simpler language means that new teams can hop onto a pre-existing project faster, because they don't have to scrutinize each LOC to the same extent.
I might be wrong on any or all of this, but that's how I perceive it.
There are other languages I can take out of the bag like Mesa/Cedar, Interlisp-D, Smalltalk-80, although they are already on the 80's borderline, which then also brings Ada, Standard ML and Object Pascal into the picture.
That may offer consolation to those who think that certain languages are the best, and who wonder why those "best" languages are used so little. But I don't think it's true. I don't think programmers are stupid, ignorant, or sheep. I think if a tool offers them advantages, they'll use it.
Perhaps I should qualify: significant advantages. There is a cost to switching. The switched-to language has to be enough better to pay back the cost.
I remember what Sun did with Java back in the day. No, I do not agree that Go has been massively marketed.
I will admit, however, that Go was created and supported by Google, and that mattered. It mattered, not in the publicity, but in the tools and libraries (and maybe even in the tutorials).
So your point is because Java has been marketed more then Go that Go is not massively marketed? If you take the set of all languages and would make a sorted list of how many hours and dollars where spend to market it then I would be very, very surprised to not see Go in the top 5.
C++ has vendors behind it. Those vendors make actual dollars selling C++ (or at least tools that work on C++.) When Microsoft markets Dev Studio (with C++ support), is that marketing C++? How about C#?
In contrast, what does Google do to market Go? Put up a website, and put out notices of the next version? When have you ever seen an advertisement for Go? For a tool that supports Go? How about for C++, C#, and Java?
Advertisement is a subset of marketing. Back in the day when Go was "new" You saw daily posts on many programmer focused communities about it. Google bankrolled the entire development of Go. Blogposts of many googlers talking about the language. Google sponsored Go events. The positive image of Google itself at the time of Go initial introduction.
You try to weasel me towards C# and Java when those are not two language I even have mentioned. But since you want to hear about. Yes both of these were also heavily marketed.
Or, if the big company doesn't do anything more than is done for other languages (Rust, say), what difference does it make? Is Rust marketed in the same way Go is? Per your definitions, I would say yes, even though the Mozilla Foundation isn't a big company.
But then, is Haskell marketed in the same way? I see lots of posts on it, at least here. Lots of blogposts on it. But there's no big company, or even foundation, behind it. Is that marketing?
Why do you define a set of actions as marketing when people who work for Google do it, but not when others do it?
Go have limited capabilities type system. C have limited capabilities type system. Both are quite successful. If talking about C I didn't shown "intellectual superiority", maybe it would be incorrect label if I where to talk about Go?
(Edit: Perhaps I should explain that last bit. Actual new users never comment this way—only seasoned users, and the ones who do it are usually using new accounts because they've behaved badly with earlier ones.)
My other comment that you're calling a "supercilious dismissal" also received 31 upvotes and some interesting responses, fwiw.
It frequently comes with labels like "echochamber" and "groupthink", which are also ways of elevating oneself as the noble freethinker standing against the mob, or some such image. Actual noble freethinkers never behave like this, so the whole thing is incongruent—it's simply a way of flattering oneself, which (ironically, because we all feel so unique) is one of the most repetitive and tedious things that people do. That's why we shouldn't do it on a site that's trying to have interesting conversation.
Another angle: people seem to posture above the community when they feel like they need to defend themselves against it. Presumably they want to defend themselves against some claim of i-am-very-smartness that they feel is emanating from the community, so they puff themselves up and put the community down by way of compensation. In reality, though, no one is making such a claim. HN isn't making such a claim—HN isn't a person to begin with—and certainly the people running HN are making no such claim. It rather arises in the psyche of some readers, for whatever internal reasons. The internet can be crazy-making that way.
To then go on to suggest a "no true scotsman" example of nobility wrt pointing out echochambers is also a pretty dangerous sentiment. Pointing out an echochamber is an echochamber is almost so obvious it's barely worth noting - to suggest there is anything "noble" about being a part of and/or not being aware of an echochamber is silly. Messageboards are echochambers. We are here because we have largely similar interests, and the posts on this board are pretty generally related to those interests. Obviously it is an echochamber, and obviously as I am posting here I am a part of it. I'm not sure how that means it is also not worthwhile to sometimes point out/poke fun at the obvious echochamber we're both a part of.
Anyway, best of luck moving forward.
It's just that in Haskell you have to write fewer tests to be sure you did it right, because for many kinds of mistakes you could have made, the compiler can yell at you.
The other advantage of nicer types is that they serve as part of the documentation. Exactly because the types can constraint the behaviour of implementations more than in Go. So the reader can make more valid assumptions from the type alone.
Hoogle uses those types to give you a truly extra-ordinary way to search the Haskell libraries via types. That approach would be almost useless in Go, and you have to fall back to searching via keywords etc only.
When you write code that operates on something like a monad, without caring about what the monad is, you want code that has a type like <T1>[<T2>]. In haskell syntax, it's m a. <T1> (or m) in this case is a type that takes another type as an argument to complete a concrete type. AFAIK, rust doesn't have type variables that take type variables (or types) as arguments like that.
That's part of it. Option and Result are concrete instances of Monads. He misses the Monad abstraction.
> Option and Result are Monads. Surely he misses do-notation.
Surely you are joking if you are trying to say someone writing haskell would say rust has monads in any practical sense. Sure, all languages have/can have A (or two) monad.
Hmm...
> When I first started using Rust, I really missed monads.
Option and Result are Monads. Surely he misses do-notation.
> This all being said, I think it is worth looking at the features that are prominent in Haskell that ended up going to Rust ...[list of features that Rust got from SML/ocaml]
Here are the officially stated Rust influences: https://doc.rust-lang.org/reference/influences.html
To say that Rust is a child of Haskell is really overstating the case.