5 ms·
> 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 number
by 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.
- pjscott 16y agoBelieve me, I know all about pattern matching, recursive functions, and all that fine functional programming stuff. If I wanted to write a recursive Fibonacci function, using an accumulator is the first thing that would come to mind. But that's not the point. I'm not asking anybody to throw out pattern matching, or recursion, or any of that. I'd just like to be able to write locally non-functional code. Look at your native Erlang implementation of fib again. Would you say that it's easier to understand than the version I posted which uses a for loop? It uses an accumulator and an auxiliary tail-recursive function; I know you get used to writing like that after a while, but I'd like another option. Haskell demonstrates conclusively that this is possible. We don't have to choose between pattern matching or mutable variables; we can have both, cleanly, as long as there's a way of keeping the mutable variables isolated in a safe box where they can't affect the rest of the program. In Haskell, this is the State or ST monad. In my proposal, mutable variables would be function-local, and such code is transformed to use tail-recursion. (This is what Haskell's State and ST monads do behind the scenes. The same could be done by a compiler with Erlang as the back-end.)
- gordonguthrie 16y agoBut why would you want to do that? This is the sort of madness that led C++ into hell. Sometimes '=' is this sort of operator, sometimes it is that. The old Perl maxim of make it possible to write the same thing in sixteen different ways is another fresh hell. 85% of the cost of softare is in code maintenance. Suddenly spawning multiple idioms for the same thing is madness. This proposal would directly increase the cost of running my software house - just not ever a good idea.