Hacker News new | past | comments | ask | show | jobs | submit login
Rust for functional programmers (poss.name)
199 points by valarauca1 on July 13, 2014 | hide | past | web | favorite | 52 comments



Sounds as if there are some bits of the article that could be tightened up or corrected, but I found the article very helpful. It's been about 10 years since I've coded in a C-style language (Java, C#, C++), but I'm very familiar with significant-whitespace languages (Python, Haskell, Coffeescript). The C-style-ish-ness of Rust has colored my perception of the language: the features sound great... but it looks like C? Admittedly, an unfortunately shallow perspective, but we've all got a lot of ground to cover and the view from 10km is necessarily incomplete.

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:".


I find it odd that you're dividing languages based on syntax. Saying "significant-whitespace languages (Python, Haskell, Coffeescript)" suggests that there's some sort of commonality between them, when they're really vastly different languages. It's almost like saying "blue-white-red countries (France, Cambodia, Russia)" :P


     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.


> "blue-white-red countries (France, Cambodia, Russia)"

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é!


Thanks for the suggestion about side effects. I added a small sentence to that effect.


The stuff about boxing, Rc and Gc is simply wrong. For example:

`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.


The "stuff about boxing" has been discussed on the reddit thread, and I will have it fixed by tomorrow. Thanks for highlighting.

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.


I'd like to know what is exactly wrong, I tried to look for the reddit thread, assuming it would've been this one:

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...


Also, unless things have changed significantly in the past year or so (since I've last really used Rust), there are significant differences between Rust traits and Haskell type classes. You can have collections of heterogeneous types that all share the same trait, which is not true with type classes. Most descriptions I've seen say that they are more of a hybrid of Java interfaces and Haskell type classes.


You can have the same thing in Haskell using existential types.


I think the point is that traits are different from typeclasses


Not really, since the equivalent formulations in Rust are also existentials, just slightly massaged syntax-wise.


It's funny, this article is dripping with attitude about how Rust will bring poor naive C programmers who are caught in the 70s into modern language design.

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.


Your comment about merging two schools of thought reminds me of two blog posts by the designer of Rust:

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)


Great links, thanks! I'll have to sit down and read these in detail when I have some time.


I'm not so sure that I agree with your perception of the tone. The high-level takeaway I got from this article with respect to C was, "C's abstract machine is clearly the best way to think about performance sensitive programming, but the type system sucks". Is that not a fair statement?

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.


I just re-read it and the tone didn't strike me in the same way as the first time. Mea culpa.


They're already did. It was called Lisp Machines, but the market favoured the "cheapest" text UNiX systems instead.


>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.

It's not like immutable variables are some magic bullet...


I'm not sure if you're joking, but when you have immutable variables they're pretty much like the mathematical variables that we've learned in school, and the benefits from that can be extremely huge (to the point that concurrency becomes easy). Whether that's considered a magic bullet is up to opinion, I guess.


>I'm not sure if you're joking, but when you have immutable variables they're pretty much like the mathematical variables that we've learned in school, and the benefits from that can be extremely huge

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.


I agree with you. I do not think immutability is good just because of concurrency; in fact that is never my motivation for taking advantage of immutability. I do it because then I have fewer bugs than if I used mutable memory cells.


> 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.

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.


As a Haskell dev about to get into Rust (and appreciates occasional parallels to OCaml) I found this very insightful. I especially like the attention given to the tables of language literals.

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.


Thanks for those comments, I have incorporated them in the document.


Just tried this example. When is not in Prelude, so it is necessary to import Control.Monad to use it.


This is interesting, although it contains a few misconceptions/mistakes/typos.

http://www.reddit.com/r/rust/comments/2al19j/rust_for_functi...


You sir are a very patient and enlightened commenter. I am grateful for your detailed comments! I'll answer in detail in the reddit thread.


I've been very hungry for some rust doc ... as everyone knows, past some good introductory stuff, a lot of detail is not documented yet. I'd vote you up +100 if I could. Thanks for this.


Nice article! This Haskell is incorrect, though:

    {- 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`.


Rust traits only don't support higher order types because they are missing from the whole language, i.e. it's not a philosophical difference between traits and type classes.

Also, Haskell has existential types, which are exactly like Rust's trait objects.


Rust has generics, which are higher order types (you can't have just a vector, it needs to be a vector "of something"). I'm not sure if the rust team would describe them that way, but there doesn't seem to be that much of a theoretical difference.

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.


> Rust has generics, which are higher order types (you can't have just a vector, it needs to be a vector "of something"). I'm not sure if the rust team would describe them that way, but there doesn't seem to be that much of a theoretical difference.

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.


I'm aware that my example isn't possible, which is why I said, "if they wanted to, they (presumably) could allow for..."

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.


> 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 ;).


I really liked this article! The side-by-sides were very well done. Thanks!


What the for loop actually expands to is much easier to read.

    [<label>] for <pat> in <iterator expression> { ... }

    let __iterator = &mut <iterator expression>;
    <label> loop {
        match __iterator.next() {
            None => break,
            Some(<pat>) => { ... }
        }
    }


It doesn't actually expand to that any more. You can see what it does expand to by running rustc --pretty expanded with a recent compiler:

  for pattern in iterator {
      body
  }
becomes

  match &mut iterator {
      i => loop {
          match i.next() {
              None => break ,
              Some(mut _value) => { let pattern = _value; { body } }
          }
      }
  }


Much easier to read to who? Not to me at least.

Code readability is subjective and depends on experience and familiarity.


I did not know that for was actually expanded early in the compiler and that one could print out the expansion with --pretty expanded. Color me enlightened. I have incorporated this, thanks!


I like a lot things about Rust. But on the downside it's syntax feels a little too much like C for comfort. Working with pointers to this extent has never been my idea of productive programming. In addition the whole Box, Rc and Arc business feels almost like a kludge. Are they part of the language or just some add on? The syntax makes it feel very much like the later.


It might be useful, from a language-evangelism point of view, to show how much of this Go doesn't do. This isn't to criticize Go--but I have gotten the impression (perhaps wrongly) that these languages are competitors.


Rust and Go are often thought of as C++ replacements, which dcauses them to be compared, however the reality is more subtle. C++ is quite flexible, and so it finds itself in many niches where there were no languages to fit. Rust and Go are targeting two of these niches, and have the ability to compete with C++ in them.

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.


> Go is designed to take advantage of large amounts of concurrency, while still providing good performance (think cloud).

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.


That is basically what this article (recently on HN) does: http://yager.io/programming/go.html


Thanks for the link, I have added it in the references of my article.


> It might be useful, from a language-evangelism point of view, to show how much of this Go doesn't do.

I disagree, since Go is not a functional programming language.


There's a missing semicolon in the Rust collatz code, let .. match { ... } ; <- here.

The Rust loop version of collatz is also missing a semicolon and missing a ! in println!.


As an outsider with only a very superficial understanding of Rust, it seems that Rust has all the right features for functional programming, except for immutable/persistent data-structures exposed by the standard library.

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.


I think once you gain a more than superficial understanding, you'll be happy with Rust. :)

> 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.


Well I definitely want to play with Rust and seems really promising, even from the point of view of a superficial observer.

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 :-)


Rust actually has two different reference counted smart pointers: `Rc`, which uses non-threadsafe reference counting, and hence is not sendable across thread boundaries; and `Arc`, which uses atomic reference counting and can be send across thread boundaries.

You can definitely create leaking cycles with both though, but Rc and Arc also support Weak pointers to safely break up cyclic ownership trees.




Registration is open for Startup School 2019. Classes start July 22nd.

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

Search: