3 ms·
Hi, I created Reia. I used to agree with you but now I don't. I was particularly moved by some of what Joe Armstrong, Erlang's creator, had to say in the book
by bascule 16y ago
Hi, I created Reia.
I used to agree with you but now I don't. I was particularly moved by some of what Joe Armstrong, Erlang's creator, had to say in the book "Coders at Work". He noted that after attending several functional language conferences, immutable state was much more a common quality of truly functional languages than other things people typically associate like static typing or lazy evaluation.
Reia is an immutable state language, and I think it's primarily a functional language, but one with a lot of features that make it less painful to use than languages like Erlang or Haskell.
In a language like Erlang, immutable state allows you the potential to send zero copy messages between actors. This can be hugely beneficial for exchanging state in a shared nothing system like Erlang.
That said, the Erlang VM doesn't work this way. There have been experiments like the shared and hybrid heaps to enable this, but they've largely been failures, not because they're conceptually flawed, but because the garbage collection in Erlang is relatively simplistic. Erlang processes are independently garbage collected so they never needed to deal with concurrent garbage collection or a shared heap before.
Erjang (Erlang on the JVM) uses Java's shared heap and lets you use the wealth of pluggable garbage collectors available on the Java platform.
In a quick test of the "chameneos-redux" benchmark on Erlang BEAM vs. Erjang, a benchmark which generates lots of messages and can benefit immensely from sharing state, Erjang was about twice as fast as Erlang.
- pjscott 16y agoI'm definitely not talking about mutable state that's shared between processes. I don't want global variables, or any such thing. I just want to be able to write little blocks of code where I can change local variables. To illustrate what I'm getting at, here's some code in JavaScript which will calculate the nth Fibonacci number: function fib(n) { var a = 0, b = 1; for (var i = 0; i < n; i++) { var temp = a + b; a = b; b = temp; } return 'fib(' + n + ') = ' + a; } It uses a for loop. Of course, you could do the same thing with a tail recursive function, or probably some higher-order-function trickery, but a for loop is a very direct way of putting it. But how do we implement this on the Erlang VM? It doesn't support any of this dangerous mucking around with the values of variables! As you rightly point out, there are good reasons for this. What we can do is convert this to, essentially, continuation-passing form. This looks awkward is JS, but it gives the same answer: function fib_cps(n) { function forloop(a, b, i, cont) { if (i < n) { return forloop(b, a + b, i + 1, cont); } else { return cont(a); } } return forloop(0, 1, 0, function(result) { return 'fib(' + n + ') = ' + result; }); } The for loop was converted to a tail-recursive function which takes the values of all the mutable local variables and tail-calls some function with (some subset of) these mutable local variables. Since Erlang has proper tail-call support, this should be no problem. And it doesn't require any modification to how heaps are handled, or Erlang's garbage collection scheme, or anything. It "just" takes some compiler work. That's what I had in mind. Does it sound more reasonable?
- bascule 16y agoYes, I've wanted to implement for loops and while loops that can alter the local binding for awhile (that compile to separate functions in the same way Erlang compiles list comprehensions to separate functions) I still haven't gotten around to it...
- gordonguthrie 16y ago> You could do the same thing with a tail recursive function Well, that's one way of putting it. This is what a native Erlang implementation of fibonaci numbers looks like: -module(fib). -export([fib/1]). fib(0) -> [{0,0} | []]; fib(1) -> [{1,1} | fib(0)]; fib(N) when is_iteger(N) andalso N > 0 -> fib1(2,N,fib(1)). fib1(N,N,[{_K1,V1},{_K2,V2} | _T]=Acc) -> lists:reverse([{N,V1+V2} | Acc]); fib1(I,N,[{_K1,V1},{_K2,V2} | _T]=Acc) -> fib1(I+1,N,[{I,V1+V2} | Acc]). It is not that you COULD do it with recursive functions - recursive functions are the native idiom of functional programming - it is how you DO do it. Erlang is a functional programming language and as such it does a lot of list processing. Where imperative or OO programmers use for loops functional programmers work on lists, a lot. Yes, it will seem strange at first, but that the nature of the beast. It is worth understanding why it looks odd: * it uses pattern matching (what's that?) * the function fib/1 has 3 clauses and fib1/3 has 2 (WTF?) * it contains list operations (eh?) Unless you understand those three things you can't write Erlang. Syntatic sugar to hide them from you is not your friend here. The function fib/1 has three clauses - if N is 0 or 1 it returns the starting elements of the fibonaci sequence. If it is an integer greater than that it calculates the sequence using the function fib1/3 The function fib1/3 has 2 clauses. The first is the terminal clause - when I have counted up from 2 to N return the sequence. The second clause is the recursive clause, calculate the current fibonaci number, stick it on the end of the list of all the fibonaci numbers calculated so far, and call myself for the next value up. Acc is a list [{I,V1+V2} | Acc] is a new list with the new value {I,V1+V2} at the head of it. In the function clauses of fib/3 I pattern match out the last 2 values of the fibonaci sequence as they are what I need. Going into the clause the accumulator list looks like this: [{11,89}, {10,55}, {9,34}....] I need the last two values to calculate the current one, so I do a pattern match: [{_K1,V1},{_K2,V2} | T] = Acc (There is an Erlang convention that 'variables' whose names start with '_' are scratch - you are not going to use them, this is why my key values are called '_K1' and '_K2'). Pattern matching says 'if this pattern matches' stick the appropriate values from the righthand side into the as-yet unmatched 'variables' of the left hand side. Because V1 and V2 have not yet been given a value they aquire the values of 89 and 55 in this instance. The terminal clause (ie the first clause of fib1/3) also uses pattern matching: fib1(N,N,Acc) -> This clause is only executed when the first 2 parameters are the same. The mistake you are making is thinking that Erlang doesn't have a for loop like Javascript or Ruby, when the real difference is that Erlang doesn't have an '=' operator like Javascript or Ruby. In Erlang '=' is a pattern matching operator. You can't set the value of an Erlang 'variable' to anything, but that doesn't matter because Erlang doesn't have variables. Pattern matching as a core language feature has as a consequence an absence of variables, which has as a consequence the absence of for loops. Throwing out the baby of pattern matching for the bathwater of for loops is to miss the point big style. Learn to love lists, learn to write recursive functions, learn to pattern match - you can't do functional programming without them.