
Why Rust ditched pure functions - vimes656
http://thread.gmane.org/gmane.comp.lang.rust.devel/3674/focus=3855
======
kibwen
I just want to reiterate something that I say in a child comment in this
thread: thanks to its thoughtful design, many of the advantages of purity are
less appreciable in Rust than they would be in, say, C++.

In Rust, everything is immutable unless you opt-in to mutability. Looking at a
function signature will tell you which of its arguments can possibly be
mutated. Global mutable state is _highly_ discouraged, by requiring you to
wrap code that accesses global mutable state in a dreaded `unsafe {}` block.
As for optimization capabilities, LLVM itself can (AFAIK) infer when functions
are "pure" and mark them as `readonly` or `readnone` (not sure what the
limitations to this approach are, though).

So don't make the mistake of thinking that Rust is a free-for-all due to the
long-ago removal of its `pure` keyword. Many (dare I say a majority?) of the
nice features of purity are merely provided by different mechanisms (for those
use cases that Rust does not satisfy, Graydon's own explanation should suffice
regarding their enormous added complexity in a language that is not Haskell).

~~~
cmrx64
Indeed, we mark them as readonly/readnone when we can.

------
christianpbrink
Since this author is reasoning through proposed purity in Rust by comparing it
with purity in Haskell...

\- I personally have never felt constrained by Haskell's purity. During
development I use `Debug.Trace` to do any debug printing I need to do in pure
functions, and I design a program so that in production code I can do
appropriate logging before and/or after any calls to pure functions.

\- Managing monad stacks in Haskell isn't that tricky. This is the kind of
thing that people get scared away from not because they actually tried to
learn it and couldn't, but because people make it out to be so hard.

\- The Haskell STM example the author links to is actually really simple,
especially in terms of monad stacks. It seems dense at first glance only
because of the `forkIO`, `timesDo`, and `milliSleep` calls, but you would need
these functions' logical equivalents no matter what language you wanted to
implement this example in.

~~~
twoodfin
I am a Haskell outsider, but I've heard SPJ say several times that laziness is
what enabled/forced Haskell to stay pure. My understanding is that even
advanced Haskell programmers struggle with predicting the runtime cost in
space and time of the laziness that bought purity. That unpredictability is
highly undesirable in a systems language.

There are certainly ways to have purity without laziness, but it's not
straightforward for Rust to adopt the Haskell model, I think.

~~~
rollo
I'm a Haskell newbie, and when I test a program and it shows a laziness-
related space leak, I just put strictness annotations on the parameters I
suspect of generating large unevaluated thunks. This has always made the issue
go away so far.

~~~
catnaroek
But, would you not agree that it would be really nice to have separate strict
vs. lazy types? It would bloat the syntax a little bit (e.g., Coq needs
separate Inductive vs. CoInductive, Fixpoint vs. CoFixpoint keywords) but the
benefits in terms of not having to find space leaks via debugging and
profiling would be enormous.

~~~
trobertson
There are several libraries that do distinguish between strict and lazy types.
containers [1], for example, separates strict Maps and IntMaps.

That said, introducing new syntax to annotate strictness is far more
troublesome than separating strict types from lazy types at the module level,
where switching between the two is a comment away:

    
    
           import qualified Data.Map        as M
        -- import qualified Data.Map.Strict as M
    

Introducing syntax would make switching between the two far more painful.

[1]:
[https://hackage.haskell.org/package/containers](https://hackage.haskell.org/package/containers)

~~~
catnaroek
When I say sometimes I need strictness, I really mean it. (Without ugly hacks
like deepseq, that is, because I am fundamentalistically opposed to hacks.)
WHNF does not quite cut it. This is not to say that I dislike laziness. I want
first-class support for _both_ strictness and laziness.

When you come to think about it, a separation of strict types and lazy types
makes perfect sense. For example, I want to foldl on lists (strict) but foldr
on streams (lazy). I do not want to accidentally use the wrong operation on
the wrong type. it is tiny details like these which make a type system helpful
for guaranteeing correctness and improving performance.

So, if strictness and laziness are both useful and distinct from one another,
why not provide both as core language constructs? Why do languages always have
to be biased towards one of them?

------
qznc
Interestingly, D has this and everybody considers it great. The community
consensus is that pure should have been the default, but it is not changed for
backwards compatibility.

[http://qznc.github.io/d-tut/idiomatic.html#purity](http://qznc.github.io/d-tut/idiomatic.html#purity)

~~~
frozenport
You would only use D if you considered it great.

~~~
Tuna-Fish
There are other reasons to use D, and indeed, most of the community came from
languages without pure functions and did not want the feature before trying
it.

However, it's one of those things where once you use it for a while, you begin
to _really_ appreciate it. Better optimization, autovectorization and cpu ->
gpu code conversion are all unimportant reasons for checked purity in the type
system. The important reason is that by constraining what functions do, it
makes reasoning about complex systems easier.

Generally, after you've worked in maintenance on one large project that
designed everything to be pure unless there's a very compelling reason not to,
you start to really miss the feature when you go back to projects in languages
without it.

~~~
catnaroek
Agreed there. But, on the other hand, D misses out on statically checked
resource management, and IMO that is more important than purity in the niche
Rust and D are targeted at (systems development). Of course, nothing prevents
a language from having both. :-)

~~~
WalterBright
Purity has a large advantage when writing concurrent code, as pure functions
don't need to be synchronized.

This is why D has enforced purity (via an attribute) rather than inferred
purity.

------
vimes656
The corresponding reddit thread at /r/programming:
[http://www.reddit.com/r/programming/comments/1t8y6g/why_rust...](http://www.reddit.com/r/programming/comments/1t8y6g/why_rust_ditched_pure_functions/)

------
hawkharris
This discussion is very interesting to me because I am just starting to learn
about pure functions and functional programming in general.

Coming from an OOP background, I am used to bundling functionality into
objects that loosely represent real-world people, places or things, but I'm
starting to experiment with using these more abstract "pure" methods.

For example, I'm working on a GPS-based JavaScript game that uses a "check-in"
system to encourage users to travel spontaneously. Initially, I designed a
tightly encapsulated "Check-in" object responsible for fetching & interpreting
users' locations.

Inspired by reading about functional programming on HN and elsewhere, I'm
trying to break up some of the general geo-processing logic such as looping,
array filtering, map/reduce, etc., into general functions with predicable
results that can be used throughout my application.

It's definitely a different way of thinking that I'm not entirely comfortable
with yet, but I can see how it allows for faster, more efficient code. I have
been able to replace 50-line code blacks with 10-line blocks that are more
efficient and - believe it or not - legible.

------
maxcan
nice to see a thread I started months ago on the HN homepage

~~~
timClicks
Has anything changed in your thinking since posting the original mail?

~~~
maxcan
Not sure. I've been less involved with Rust in the past few months.

