Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This is full of great advice. After a brief skim I think I agree with everything except the advice on mutability.

Haoyi says "if you are actually modeling something which changes over time, using mutable [variables] is fine". I think this is absolutely wrong. You should even use immutable variables to model things which change over time (after all, almost everything does). Only use mutable variables when performance means you can't get away without them.

Other than that the guide is pretty much how I would program (as a Haskell developer, rather than a Scala one).



I like immutability too. It's just sometimes rather inconvenient.

I would agree with you if you could write something like

http://www.lihaoyi.com/roll/

in a pure immutable style and show me how it's better. You will start off with lots of `.copy`s, maybe transition into lenses and other optics, and soon you'll be on the bleeding edge of research and still not have a working game that's maybe high-school difficulty if written in a mutable style

Research is cool and immutability is cool but if I can get what I want only needing some highschool kid instead of a PL-PhD that's cool too


Ah, but that's a different claim. The claim in the article is "You can use mutability to model things that change with time.". Your claim here is "You can use mutability if it makes programming easier.".


How on earth are those things related? Because using mutability to model mutable things makes programming easier, while using mutability to model immutable things makes it harder. It's not rocket science...


My intention is to try to tease out the finer distinction between where immutability should be used and where mutability should be used, because I'm personally very interested in the tradeoffs.

I'm genuinely sorry if my comment touched a raw nerve. As I said, I think it's an excellent article.

One thing I got wrong in my original comment: use mutability where performance means you can't get away without it, but also where code complexity means you can't get away without it. (My personal view is that there are many cases of the former but few of the latter, but that's an argument for another day.)


You didn't touch a nerve, I'm just being snarky because it's the internet and if I don't the internet police will arrest me.

"code complexity means you can't get away with it" seems very common. Five nested `.copy` calls is probably "too much". Lenses, in most of the incantations I've seen so far, are probably "too much". I'd love to see your solution for making

    a.copy(b = a.b.copy(c = a.b.c.copy(d = a.b.c.d.copy(e = 1))))
As easy as

    a.b.c.d.e = 1
Closest I've seen is QuickLens, which is promising. Still pretty new and experimental, but definitely something I could see myself using:

    modify(a)(_.b.c.d.e).setTo(1)
Still slightly clunky, but less clunky than the other implementations I've seen e.g. in Monocle


The apparent impossibility of a simple lens syntax is one place where Scala is letting me down. I hugely appreciate all the work that's going into things like Dotty, but I wish people were equally as enthusiastic about basic language usability improvements.

One of the most important things a language can do, IMO, is make basic operations easily expressible. Lensing is one of those things.


This [1] part of an interview with Rich Hickey is great on this topic.

[1]: https://youtu.be/wASCH_gPnDw?t=37m13s


I'll add to this that it's precisely when values change over time that you want immutability most.

Because when dealing with time, you get a lot of non-determinism when modifying values in variables. This happens because the ordering of non-commutative operations is important, mutable things have identity because their "value" changes with the history of those operations and so with variables and mutable data-structures it gets very hard to reproduce a certain state or to reason about the sequence of events. So in case of errors, it's hard to reproduce a bug or to reason about what ordering you had. Note this isn't to say that operation ordering issues won't happen with immutable data-structures and values - it can happen of course, though having values and pure functions in that chain will make it easier to debug.

But then if you introduce concurrency by multi-threading in the mix, that's when the shit really hits the fan. Because then you have to guarantee a certain ordering and you do that by concurrency primitives, most commonly by intrinsic locks and for one dealing with this stuff is really hard and non-intuitive and also locks are not composable and in general a bottleneck (Amdahl's law ftw). This is why people end up using `Future` and actors and other higher level abstractions for dealing with concurrency, because they make it easier to reason about ordering. But for those that ever worked with Erlang, the "receive" function of actors is meant to be pure, instead of modifying mutable variables, like people normally do in Akka. And in Akka you can work like in Erlang (e.g. context.become), but people rarely do that. And of course, you then get the most horrible mutation known in the software industry, because in addition to having shared mutable state, you're now also dealing with asynchronous message passing on top of that.

As a final word: having the ability to mutate is having power and when mutating variables you're using that power. Sometimes it's necessary, sometimes it's convenient, but many times it's more power than you need. You don't get the luxury of mutability when dealing with accounting for example ;-)


   it's precisely when values change 
   over time that you want immutability 
   most.
Values always change over time, otherwise you could replace variables by constants.

I suggest that immutability is most useful for data that crosses into a context that you (the programmer) have no/little control over. There is nothing wrong with a bit of state, like so

  def pow ( n : Int ) : Int = {
    var result = 1
    var i = n
    while ( i > 0 ) {
      result *= 2
      i -= 1 }
    return result }
The reason this is OK is that the state changes are local, while globally (from the outside) pow remains functional. There is no need to coordinate with other programmers, or other functions or modules, to get i and n to behave properly.

In contrast, if you pass shared state into a context that you have limited knowledge or control over (e.g. shared memory concurrency) then things become hairy.

That's why I advocate: "locally stateful, globally stateless" programming.


Just a note, variables are always replaceable by constants ;-)

   @tailrec
   def pow(n: Int, result: Int = 1): Int =
     if (n > 0) pow(n - 1, result * 2)
     else result
But yes, mutability isn't problematic if it's well encapsulated. Works well for simple functions, but when you get to more complicated things (e.g. classes/modules) the problem is that proper encapsulation is very hard to do, sometimes next to impossible.


Sure, we can always translate away state and pass it explicitly.

Some people find some algorithms can be more naturally expressed using state. (And "some people" is a polite way of saying "alot".) The question of whether state can always be replaced by pure functions without loss of asymptotic complexity is open as of April 2016. All I wanted to point out is that there is no need of becoming a functional Taliban: if state is kept local, that's usually fine.

   when you get to more complicated things
Yes. Then don't use it. My rule of thumb is: definition and all uses of state should be visible to the programmer without scrolling.


Note that although many prominent Scala people program in a very Haskelly style, mutable variables are significantly more idiomatic/first-class in Scala than they are in Haskell.


I agree.

The fact that Scala is proper multi-paradigm, and has a first-class functional sublanguage as well as a first-class imperative sublanguage is part of its great appeal.

I have mentored quite a few people over the last couple of years in Scala. In my experience, going from imperative Java, C, Python straight to Scalaz/Typelevel/higher-kinded monad style pure functional programming never works, and drives Scala-beginners away.

What works extremely well is to start with Scala as a nice Java without semicolons, and then, as they become more proficient in the OO/imperative Scala fragment, gradually to introduce more advanced features, starting with case classes.

The reputation Scala has for being too difficult comes from certain parts of the Scala elite trying to convince everybody, including beginners, that higher-kinded purely functional Scala is the only way to use the language. Nothing wrong with this style of programming per se and it's great that people are pushing the envelope, but the conceptual gap is too big for a language learner hailing from the imperative world (which is most of them).


Oh I'm sure that's true, but as a recovering Python programmer I'm uncomfortable with justifications of mutability under any except the most extreme circumstances. ("In our codebase we avoid writing side effecting code", my colleagues said, whilst filling the codebase with side effecting code.)


This, too, will pass. Mutability is fine, if isolated and carefully accounted for. However, to understand when it's fine and when it isn't, you have to bring years of experience both in imperative and functional world.


I just sidestepped the issue by becoming a Haskell programmer and I'm glad I no longer have to deplete my judgement by making those kind of decisions :)


There's value in that, sure. But when programming one is generally modeling a business process, and some of those processes really are easiest to understand by thinking in terms of a thing that changes. So it's nice to have a programming language that lets you talk about things that change in a natural way.




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

Search: