Rust for functional programmers
science.raphael.poss.name
science.raphael.poss.name
The article's side-by-side examples really opened my eyes. I understand that Rust doesn't implement all of the functionality of Haskell, but it's comforting to see analogous functionality. So Traits aren't Type Classes; but as a beginner in Rust and as a first-order approximation, I can hear sorta-Type-Classes when I see Traits. That'll get me past the first steps, then I can dig into the detail of Traits.
I would suggest the author include a section on Side Effects since that's a big talking point around functional languages. Some allow them pretty freely (Ocaml) and some don't (Haskell). The only place I really see mention of this Big Deal is a little header to a code comparison saying "Recursion with side effects:".
As distinct from "red-white-blue" countries (USA, Cuba, North Korea) or "blue-red-white" countries (Netherlands, Australia, Iceland).
After all, most programming languages are written as lines of code that represent statements that the computer must execute in some comprehensible order. Wake me up when this metaphor is deemed passé!
suggests that there's some sort of commonality between them
There is a commonality: they use significant whitespace. I understand that this is a syntactic difference and, given that I said I had used each quite a bit, that they're quite different languages. That said, syntax is chosen for a reason and I feel most comfortable in the significant whitespace world. I'm excited by Rust but, shame on me, the syntax had confused me into thinking the language was more C-ish than Haskell-ish. My point in the grandparent was that the article did a nice job of changing my (admittedly shallow) opinion.`box foo` is Box<T>, the old ~T, nothing to do with Gc or Rc.
Gc is not at all clever and does not do cycle collection.
There are quite a number of other wrong or slightly wrong things about Rust, and a total ignoring of many Rust conventions.
As to "Gc not being clever", the code is clear that it is intended to be clever in the long run, so the essence of the message could stay. Maybe the wording could be made more precise, thanks again for highlighting.
As to the "number of other wrong or slightly wrong things", I would be more than happy to fix the text if you have concrete remarks.
http://www.reddit.com/r/rust/comments/2al19j/rust_for_functi...
but there're no comments as of now
EDIT: found it
http://www.reddit.com/r/programming/comments/2akz51/rust_for...
You could write just the same kind of article about how Rust will bring out-of-touch theorists into modern software engineering where you actually need a C-level of performance and predictability. And then show examples of all the same "for" loops with mutable variables that you're used to writing in C have direct analogs in Rust.
But both bits of snark would miss the point, which is: Rust is awesome precisely because it merges these two schools of thought into something that preserves the advantages of both.
Part 1: http://graydon2.dreamwidth.org/3186.html (discussion: https://news.ycombinator.com/item?id=8026868)
Part 2: http://graydon2.dreamwidth.org/189377.html (discussion: https://news.ycombinator.com/item?id=8028255)
Well the 'Motivation' section is about how low-level languages are a better (closer) fit to the contemporary machine architectures than functional programming. That could be interpreted to be dripping with attitude about functional programmers being out-of-touch with how machines 'really work'. (Which also seems to miss the point that functional programming languages that care about performance tries to achieve that by transforming idiomatic functional code to efficient imperative code, not trying to leverage some kind of machine model that actually matches the semantics of functional programming languages.)
> And then show examples of all the same "for" loops with mutable variables that you're used to writing in C have direct analogs in Rust.
Iterators seem to be more idiomatic in Rust than the loop alternatives.
I suppose there was an intro bit about Rust's lack of advertisement as a functional language (and perhaps that comes off as snooty), but from what I've seen of programmers outside of HN, this is a reasonable precaution. Most others I've talked to are either simultaneously ignorant and apathetic towards FP or actively distrustful. I literally had a friend at a major tech company tell me that he avoids FP because he does "real work", and he shouldn't have to bother with "idiots and their lambdas".
Then again, everyone likes to feel like the downtrodden victim. Dropping the pretense on both sides and trying to elucidate without proselytizing is the most effective strategy.
It's not like immutable variables are some magic bullet...
Yeah, I've read all the relevant stuff and seen my share of Hickeys presentations, Haskell folklore etc.
Still, I don't think it's some magic bullet. Maybe because IMHO, concurrency is just the latest fad IMHO. It's like: "OMG, we have so many cores and no more Moore's law, we HAVE to use them".
E.g on mobile apps. If concurrency (for speed) was important, people wouldn't use frameworks based on non-native scripting engines, adding 10 tons of overhead to their users resource constrained mobile phones.
While concurrency makes sense for server infrastructure and DSP stuff, for most of the problems programmers solve everyday (in web applications, in desktop applications, in enterprise applications, etc), it's really not that important. Not just because there are also processes, rpc, Node like async and similar strategies one can use (including APIs like Apple's GCD), but because it's not really about getting top speed, or most of the apps doesn't even come close to saturate 20% of a single core.
And there's always the graphic card for DSP and parallelism -- where it doesn't matter that much if your language has immutable variables or not, because you'll use CUDA etc for that.
Not that it takes away from the purpose of the post but I couldn't help but see some issues in the Haskell code.
The code under "Recursive data structures" is not valid, top-level code in a Haskell source-file isn't evaluated like in Ocaml, I'd remove the "let" keywords and put "main = " in front of the putStrLn function (which is also misspelled).
As others have mentioned, the ADT example is wrong, you need to use "data" instead of "type", and the convention for the optional type in Haskell is "data Maybe a = Nothing | Just".
hello x =
test x
This might be intentional, but it's very unconventional to put a newline after the equality sign in that short function. if v /= 1 then collatz v
else return ()
This is better written as: when (v /= 1) (collatz v)
and compares better with the Rust and OCaml code.http://www.reddit.com/r/rust/comments/2al19j/rust_for_functi...
{- Haskell -}
type Maybe a =
None
| Just a
{- x : Maybe t -}
case x of
None -> False
Some _ -> True
In that snippet, `type` needs to be `data`. Also the canonical constructors are `Nothing` and `Just` (but that doesn't have to do with correctness :))There are also a few times where you use a single colon for a type annotation in Haskell, instead of a double colon.
Also, I disagree with this:
> Rust's “traits” are analogous to Haskell's type classes.
> The main difference with Haskell is that traits only intervene for expressions with dot notation, ie. of the form a.foo(b).
That's more of a syntactic distinction; the difference at a semantic level is much bigger: Haskell type classes extend to higher-order types. For example, Functor and Monad rely on a type with kind `* -> *`, and it can get more complex than that, for example with monad transformer classes (like State and Error).
Also, an important difference is that Rust traits can be used as Java-style interfaces; for example, you can have a list of objects of different types, but that all implement `Show`.
To be a tiny bit nitpicky here: you can have a list of pointers to objects of different types. Vec<Show> is illegal, Vec<&Show> is not.
Still, it's like Java in that regard ;).
Also, Haskell has existential types, which are exactly like Rust's trait objects.
Thus if they wanted to, they could (presumably) allow for a higher-order trait like Functor:
trait Functor {
fn map<a, b> : (|a| -> b) -> Self<a> -> Self<b>
}
(I'm not sure if that's valid syntax but you get the idea).As for existential types, they're an area of Haskell I haven't really explored that much, but they seem to be seen as a bit more of an advanced feature than their equivalents in Rust or Java. Not just because they require a GHC extension and additional keywords, but because there's a bit more theoretical overhead to understand how they work, while the presentation in Rust/Java is more intuitive and straightforward.
No, there's no first class "type level functions", i.e. you always need `Vec<T>`, and you can't ever write `Vec` by itself, which is what a true higher-order type is (and what Functor needs:
impl Functor for Vec { ... }
impl Functor for Option { ... }
Thus, your example isn't at all possible now or even in the near future.My point being that Rust restricts what one can do with higher-order types, but those types still exist and are expressible in the language. From a theoretical point of view, there doesn't seem to be any reason that they couldn't allow for traits on higher-kinded types -- they simply choose not to. Perhaps for performance reasons, or for simplicity, or for syntax or whatever, but I haven't seen any reason why they couldn't. That was the point that I was making.
[<label>] for <pat> in <iterator expression> { ... }
let __iterator = &mut <iterator expression>;
<label> loop {
match __iterator.next() {
None => break,
Some(<pat>) => { ... }
}
}Code readability is subjective and depends on experience and familiarity.
for pattern in iterator {
body
}
becomes match &mut iterator {
i => loop {
match i.next() {
None => break ,
Some(mut _value) => { let pattern = _value; { body } }
}
}
}I disagree, since Go is not a functional programming language.
Go is designed to take advantage of large amounts of concurrency, while still providing good performance (think cloud). Rust allows low-level control while giving the OOP structure required for large programs, like web browsers, or even game engines. Go was also (mistakenly) referred to a systems language at one point, which didn't help the issue.
Java/JVM and C#/.NET fall in that space as well and are doing a very good job, while at the same time exposing a managed runtime that can be used as a target for other languages, thus lowering the friction for bootstrapping such new languages. Because of this, in a sense, Java/.NET are better replacements for C, which has also been used for building portable libraries freely usable across multiple languages. I'm still getting thrills when using well tested, mature Java libraries from Scala, Clojure or JRuby.
Where Java/.NET really suck is for building real-time or soft real-time systems, because they are managed and thus heap allocation must be managed by a garbage collector. Go has precisely the same limitation by design, with the difference that because it's younger, it doesn't have pauseless garbage collectors available for it - like say, the one from Azul Systems. Go GC is even less good than the garbage collectors available in the reference Java/.NET implementation - the one in version 1.0 at least wasn't even precise, let alone concurrent.
And this is not about performance per se, btw. This is about dealing with real-time constraints. In certain contexts speaking of the "cloud", it's not enough if your server is able to respond to tens of thousands of requests per second in under 100ms per request, if you can't guarantee that kind of delivery for more than 99.99% of requests. For video decompression, another industry dominated by C/C++, the average throughput doesn't matter if you can't guarantee a consistent frame rate per second - how annoying would it be if when watching a video, the playback would suffer short freezes every couple of seconds? To make matters worse, if the GC stops the world, it can act like a global lock (GIL) and then you become aquatinted with Amdahl's law, as it severely limits vertical scaling, even if your logic is embarrassingly parallelizable.
Go was referred to as a systems language, but I don't think that was a mistake on their part. If Go wouldn't have been referred as a systems language, then that would've meant admission that the real competition for Go is actually the JVM with all its languages running on top of it.
The Rust loop version of collatz is also missing a semicolon and missing a ! in println!.
This is the missing link for people wanting to do FP in Rust and even though this is an issue of standard library and not necessarily one of language, this will be a source of pain for people wanting to do FP in Rust, as FP means working with referential transparency, which begets immutability. This I think is worth mentioning, being the biggest road-block when wanting to adopt an FP style in any language - after all, any language that supports higher-order functions can be used for FP (or OOP) with varying levels of pain.
Again, as an outsider with a superficial understanding of Rust, it seems that Rust will make FP enjoyable in a language that doesn't need a garbage collector. But judging from articles and announcements made, Rust's primary objective to make concurrent programming safer isn't necessarily to make immutability effective, but rather to limit/control both shared reads and writes. And I can think of contexts in which you don't necessarily want that - after all, the shared reads of an immutable value are embarrassingly parallelizable, persistent data-structures working really well in say single-producer multiple-consumers scenarios. Plus immutable values are for having referential transparency and the common concurrency issues that we are commonly seeing are only a symptom. Truthfully, Rust must allow specialized algorithms and low level code, so I expect it to give the possibility of choosing your path instead of forcing a solution down your thrown.
But herein we are ending with a problem also exposed when working with C++. People have been doing FP in C++ with varying levels of success. But the problem with persistent data-structures that are doing structural sharing is one of memory management. It's much harder to implement persistent data-structures without relying on a garbage collector. LISPs that encourage an FP style, Ocaml, Haskell, SML, F#, Scala and all languages advertised as FP I can think about are garbage collected. Rust can use an optional garbage collector for "shared references". But that negates the biggest advantage when using Rust, the community is moving away from shared references and Rc/Arm touched in this article would be awful solutions to this problem.
Therefore I hope seeing progress in seeing a library of immutable data-structures that I could use in Rust or at least advice on how to deal with effectively immutable data-structures or something.
On the bright side - Rust's type system seems to be solid (yay generics, yay type-classes), which means it's just a matter of time before the libraries will get there.
> the shared reads of an immutable value are embarrassingly parallelizable,
This is absolutely true, and Rust lets you do that. You just have to dot your is and cross your ts: https://gist.github.com/steveklabnik/8de9b9b0ae2e45383d85 Rc adds reference counting, to ensure that this reference will be okay for the duration of all of the threads. If you just used a regular old boxed value, one of the threads could invalidate the reference.
That's just one example. As you say, there'll be libraries eventually: people have been focused on the language itself, and given how easy Cargo makes it (will make it) to share libraries, I wouldn't be surprised to see an awesome persistent/immutable data structures library appear.
My impression is that reference counting, while deterministic, has some problems due to reliance on atomics (which gives rise to contention issues, even in cases in which intuitively there shouldn't be any contention) and because of cyclic references - but maybe this isn't such a problem in practice when used for persistent data-structures or if there are multiple versions of an interface suited for different scenarios.
My intuition has definitely been proven wrong before. And I have high hopes for Rust - seemingly being a language allowing for FP and for fine grained memory management while not setting my hair on fire :-)
You can definitely create leaking cycles with both though, but Rc and Arc also support Weak pointers to safely break up cyclic ownership trees.