
A Language Without Conditionals - JeanPierre
http://www.hypirion.com/musings/without-conditionals
======
epidemian
The author is not complying with their own requirements:

> 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).

------
philwelch
So you just end up using function pointers and arrays to fake polymorphism. If
you have the requirement that every part of a function has to be evaluated,
you just end up branching between function calls rather than eliminating
branching. This is vaguely reminiscent of how some functional languages use
tail-recursion instead of loops--they don't eliminate iteration, they just
express it a different way.

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.

~~~
ufo
This is sad. If only people would take the Expression Problem[1] more
seriously and realise that there is a good time to use ifs/pattern matching
and a good time to use polymorphism/function arguments

[1] <http://c2.com/cgi/wiki?ExpressionProblem>

~~~
mvc
I think CLOS doesn't suffer from the expression problem. Both alterations to
the data model can be implemented as a "patch" to the program without editing
any of the original code.

~~~
ufo
Gah, do you know if there is a better name for this "pattern matching vs
polymorphism" duality then? I think its still important in its own right since
multimethods and other solutions to the expression problem usually apply to a
more global-ish scope, unlike pattern matching and functions since those are
primitive constructs and naturally nested as deep as you want.

------
paulsutter
I love the Erlang example, I've been curious about Erlang and that's a
powerful demonstration of conditional-free programming, contrary to the
author's intent.

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.

~~~
jallmann
Pedantic: "n>2" is a comparison operator, and doesn't incur a branch. if
statements, ternary operators, etc are conditionals. The effect in the article
is the same though, without the potential for a mispredict penalty.

Whether that's actually faster is debatable though. If you're writing in C,
you're probably concerned about performance, and the function pointer
invocation is going to be a lot slower than a branch mispredict. A
sufficiently complex array dereference will slow it down too, but I wouldn't
be surprised if the compiler can turn simple cases like this into an
equivalent switch/jumptable.

Thus conditional-free programming (as presented here, in C) is mostly
academic. Debug tracing is an interesting application, but readability and
performance both suffer.

If a language could internalize conditional-free constructs natively, however,
it could be an interesting study.

~~~
adrusi
`n > 2` could be a comparison operator in some architectures. For example, in
dcpu16 (fictional, but I'm sure this was inspired from somewhere), `a = b > 2`
would be written as:

    
    
        set A, 0
        ifg B, 2
          set A, 1
    

It conditionally evaluates a machine instruction, which is, indeed, branching.

------
Jabbles

        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...](http://stackoverflow.com/questions/10096599/bitwise-operations-
equivalent-of-greater-than-operator)

Oh and the "i < 32" doesn't count as you could just unroll it...

------
xentronium
If you are interested in ideas that can arise from putting some hard
restrictions in the same vein, see "programming with nothing"[1]. SICP also
explores this topic slightly in the exercise about Church numerals and thought
experiments on how you could try to reimplement special forms using functions.

[1] <http://codon.com/programming-with-nothing>

------
revelation
In this post, we don't use conditionals but instead constructs that compilers
use to compile conditionals.

------
lmm
Makes me think of the smalltalk approach to branching, where it's there, but
just another method call (on a boolean) - rather than

    
    
        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.

------
betterunix
Reminds me of this approach to conditionals:

[https://en.wikipedia.org/wiki/SKI_combinator_calculus#Boolea...](https://en.wikipedia.org/wiki/SKI_combinator_calculus#Boolean_logic)

~~~
raganwald
That was my first thought as well, replacing conditional evaluation with
"choice."

------
gmartres
Here's a real implementation of prettyprint without conditional jumps (this
assumes signed right shifts are implemented as arithmetic shifts by your
compiler, but everyone does that anyway):

    
    
      #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/> .

------
Nursie
I'm not sure I understand the purpose of this really. If it was just a thought
experiment then cool, I guess.

~~~
gatlin
I can't speak for anyone else, but I can think of one reason to do things like
this. If I can use a minimal subset of language features and verify their
correctness, and also verify the correctness of the ways in which I combine
them, then it would follow that my entire program is much, much easier to
prove correct.

This is perhaps more useful in lisps or Haskell but it's still cool to think
about. And, hell, maybe someone implementing a language in C could do
something like this for the debugging properties.

And while yes you could take this to the extreme and implement everything on
top of SKI combinators, perhaps one wants to start a touch higher up than
that. Anyway, maybe that's a useful answer?

~~~
Nursie
Maybe :)

I guess I was just puzzled as to why the author felt that avoiding
conditionals was a useful exercise in the first place. I realise that I'm
probably wedded to a conditional-heavy way of thinking, I guess what threw me
was that the author seemed to assume that eliminating conditionals stands as
an aim on its own, regardless of minimising use of language features. At the
same time he has a test in his array de-reference that seems to me to be
effectively a conditional without using the explicit if(){} else{}
construction.

TBH I would have thought verifying the implicit conditional and function-
pointer jump was no easier than the explicit...

------
Tyr42
I'd of like to seen the lambda calculus way of defining conditionals.

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.

~~~
ufo
And Smalltalk does just that, by the way. Booleans have an `ifTrue: ifFalse:`
method that receives two callbacks as parameters.

------
jpatte
From the article: _"If the compiler/debugger is able to create this "evaluate
every piece of line" format for functions (generating new ones when needed),
then we can use the stack to check which branches we've taken — they're on the
stack, after all!"_

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.

------
andreasvc
I think it is more instructive to realize that certain (most?) problems rely
on conditionals conceptually. You could then work around them by effectively
implementing conditionals anew and spreading code over smaller functions, but
the algorithm is still conditional in nature. I am also curious about the
relation of conditionals to other constructs. Is the for loop disallowed
because it checks for the end of the loop? But it could be implemented with a
map over a range, which does no such check (assuming range is a primitive
operation). In this way conditionals can be eliminated by recruiting a variety
of possible constructs, but it all depends on the rules you're setting.

------
bjourne
This is a way prettyprint could have been written without branching:

    
    
        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.

~~~
kzrdude
True and False are 1 and 0 in Python too, so no int constructor needed. (Try 3
+ True == 4)

~~~
slurgfest
True and False are not 1 and 0 in Python.

    
    
         >>> (1 is True) or (True is 1)
        False
        >>> (0 is False) or (False is 0)
        False
    
        >>> isinstance(0, bool) or isinstance(1, bool)
        False
        >>> isinstance(False, bool) and isinstance(True, bool)
        True
    

Indexing with boolean expressions is bad style to begin with, but if you are
going to do it then a bool cast shows explicitly what you are trying to do.

~~~
peter-fogg
You'll find that they are integers, though:

>>> isinstance(True, int)

True

>>> bool.__mro__

(<type 'bool'>, <type 'int'>, <type 'object'>)

Although they aren't literally the same object, 1 == True does evaluate to
True.

~~~
slurgfest
Downvoting notwithstanding, the statement that 1 and 0 ARE True and False is
flat false. It's not even kind of true: True is not 1 and False is not 0, the
type of True and False is not int, and the established convention for
comparing to these values is to use 'is' if you are not using the vaguer 'if
cond' or 'if not cond'.

~~~
kzrdude
You are missing the point. The original code was making detours instead of
using True as the list index (which you can do).

------
morganwilde
I can honestly say that the best thing to have come out of this article was
the link to Bret Victors talk on inventing on pricple -
<http://vimeo.com/36579366#t=1004>. Thank you.

~~~
themgt
Thanks for singling that out. The specific demos of interactive
coding/visualization were cool, but the end where he really gets into the
general topic of finding a guiding principle and using that to change the
world - was very, very well worth the time to listen to.

------
emdezpk
That a _terrible_ C implementation.

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??

------
EarthLaunch
It's true that first class functions can remove the need for conditionals.
Isn't this simply functional programming? He starts with an Erlang example
then doesn't talk about FP. I must be missing something.

------
mamcx
The most useful thing is the coloring of the branching. Neat.

~~~
Nursie
Have you seen gcov/lcov?

They're coverage tools rather than debuggers, but they can give you an html-
browseable version of your source code coloured like that to show you which
lines have been hit and how many times.

------
shurcooL
Thank you to the author for this article.

After reading it, there's a chance I will use some of the insight I've gained
in my Conception project.

------
JosephHatfield
Reminds me of Prolog

~~~
andreasvc
A clause in Prolog is a predicate that is true when its conditions are met,
i.e., can be seen as a single conditional in itself. Furthermore, there is
pattern matching (unification, even), which was declared cheating in the post.
Fibonacci examples would use this, just as the Erlang example in the post.
There are also if-then rules (the -> operator), which I find makes code
clearer. The same trick described in the post could be implemented in Prolog I
suppose.

