by JeanPierre on 2/10/13, 4:53 PM with 43 comments
by epidemian on 2/10/13, 11:55 PM
> Pattern matching and polymorphism is not allowed
Using dynamic dispatch on different functions of type Int -> Int is, precisely, polymorphism. Doesn't look much like the OO polymorphism that some people are used to, but FP folks use this kind of functional polymorphism all the time.
That being said, the main premise of the article is quite valuable: yes, you can program with no conditionals. And i think it's quite useful to know that polymorphism can be used like that without suffering too much of an unfriendly syntax (see lmm's comment on Smalltalk: http://news.ycombinator.com/item?id=5198419).
Finally, although pattern matching can be seen as an overpowered if-statement, the relation can be also turned around: the if-statement is a special case of pattern matching. I won't say that pattern matching is not conditional statements, but i think it's also quite valuable to notice that you can use pattern matching as a new programming primitive instead of the if-statement. For example, the if-statement implemented in Haskell:
if :: Bool -> a -> a -> a
if True x _ = x
if False _ y = y
Relying only on pattern matching instead of conditional if-statements can make quite the difference on how one reasons about programming problems (e.g. Prolog and declarative programming in general).by philwelch on 2/10/13, 6:01 PM
Where I disagree with the author is in pointing out that there is no real difference between declaring "fib(1) -> 1" in a pattern matching function definition and inserting a pointer to a function that returns 1 into an array of function pointers. Pattern matching and subtype polymorphism can even still get all the gains the author is looking for--namely, being able to debug branching by inspecting the call stack. For that matter, so would pushing a dummy stack frame every time you come to a branch, or even building a separate branch stack.
by paulsutter on 2/10/13, 7:14 PM
Sorry, but "n < 2" is a conditional, whether you're using an if statement, the "?" operator, or to reference an array of function pointers (at least from this old-school C/assembler programmer's perspective). Whereas that Erlang code looks elegantly conditional-free. Clearly branching will appear behind the scenes, but the Erlang code has no conditionals.
by Jabbles on 2/10/13, 7:44 PM
int res = (*fn[(n < 2)])(n);
As stated by others, "n < 2" is a conditional or comparison or whatever (let's just say it's cheating :P) let's call it "m".So we choose our function pointer with:
int res = (*fn[m])(n);
where uint32_t m = n < 2?1:0;
and let's assume n is non-negative... uint32_t top_bits = n & 0xFFFFFFFEu;
uint32_t p = 0;
for (int i = 0; i < 32; i++){
p |= (top_bits & (1u << i)) >> i;
}
uint32_t m = 1 - p;
Edit:You can do the for loop to check if any bits are set in logarithmic time. For general comparisons see http://stackoverflow.com/questions/10096599/bitwise-operatio...
Oh and the "i < 32" doesn't count as you could just unroll it...
by xentronium on 2/10/13, 5:41 PM
by revelation on 2/10/13, 7:40 PM
by lmm on 2/10/13, 11:23 PM
if(a > b) {...code...}
you do something like (a > b).ifTrue({...code...})
I think it's an approach more languages should use - it means there are fewer special cases in the syntax, and you can compose conditionals or use them in higher-order functions more easily.by betterunix on 2/10/13, 5:33 PM
https://en.wikipedia.org/wiki/SKI_combinator_calculus#Boolea...
by gmartres on 2/11/13, 1:17 AM
#include <stdio.h>
#include <stdint.h>
void prettyprint(int a, int b)
{
int lt_mask = (a-b) >> (sizeof(int) * 8 - 1);
intptr_t lt = (intptr_t)"%d is < %d\n";
intptr_t ge = (intptr_t)"%d is >= %d\n";
char *text = (char*) (lt & lt_mask) + (ge & ~lt_mask);
printf(text, a, b);
}
int main(int argc, char **argv)
{
prettyprint(atoi(argv[1]), atoi(argv[2]));
return 0;
}
The point is that the CPU doesn't have to guess what to execute next, so https://en.wikipedia.org/wiki/Branch_misprediction cannot happen.And if what you're interested in is using the call stack to keep track of executed code, a more effective approach would be to convert the program to CPS, see https://en.wikipedia.org/wiki/Continuation-passing_style and http://matt.might.net/articles/cps-conversion/ .
by Nursie on 2/10/13, 5:34 PM
by Tyr42 on 2/11/13, 12:10 AM
Let True = (λ x y => x) Let False = (λ x y => y)
Then to do an if, you just apply the conditional ((λ x y => x) 1 2) ---> 1 ((λ x y => y) 1 2) ---> 2
It's funny, really. Pure lambda calculus can feel a lot like a object oriented message passing style.
by jpatte on 2/11/13, 8:31 AM
No, they are not. Having called functions instead of evaluating conditional statements doesn't change anything to the state of the [non-stale part of the] stack at the time you reach the breakpoint.
To achieve this one would need to execute the code following the branching (and containing the breakpoint) as callback of any of the "conditional" functions, so the followed branch would indeed appear in the stack. But that would just be impractical as you would reach stack overflow extremely quickly - not to mention the hit on performances.
by andreasvc on 2/11/13, 12:07 AM
by bjourne on 2/10/13, 6:07 PM
def prettyprint(a, b):
fmt_string = {True:'%d is less than %d',False:'%d is higher than %d'}[a < b]
return fmt_string % (a, b)
Or even (and this is cheating by using the int constructor): def prettyprint(a, b):
fmt_string = '%d ' + ['is higher than', 'is less than'][int(a < b)] ' %d'
return fmt_string % (a, b)
I think a good way to get acquainted with FP is to replace conditionals with lookup tables and lambda functions. But it is easy to overdo it, sometimes plain old if-statements are the most readable alternative.by morganwilde on 2/10/13, 9:15 PM
by emdezpk on 2/15/13, 4:48 PM
It uses recursion, while a simple loop will suffice. Your fib(10000000000) will blow your RAM limit with growing stack (and will be slow) while implementation with loop would consume zero RAM and would run several times faster.
"And as if by magic, there's no need for conditionals" : also incorrect. You have a conditional in int res = (fn[(n < 2)])(n); specifically the (n < 2) part. All you have done after the hard work is moving conditional from one line to another.
"But we're still not sure of which branch we took." - So why don't you put the breakpoint before* the condition occurs, and step through it in the debugger??
by EarthLaunch on 2/10/13, 7:08 PM
by mamcx on 2/10/13, 7:14 PM
by shurcooL on 2/10/13, 8:28 PM
After reading it, there's a chance I will use some of the insight I've gained in my Conception project.
by JosephHatfield on 2/10/13, 6:14 PM