[erlang-questions] Why does Erlang have control structures?

100 views
Skip to first unread message

Jayson Barley

unread,
Aug 27, 2012, 4:56:37 PM8/27/12
to Erlang Questions
I am not sure I understand why we have them. For instance I can take the following code
is_greater_than(X, Y) ->
    if
        X>Y ->
            true;
        true -> % works as an 'else' branch
            false
    end.
And make it
is_true(true) ->
true;
is_true(false) ->
false.

is_greater_than(X, Y) -> is_true(X>Y).
I can do the same thing with case statements
is_valid_signal(Signal) ->
    case Signal of
        {signal, _What, _From, _To} ->
            true;
        {signal, _What, _To} ->
            true;
        _Else ->
            false
    end.
Becomes
switch_signal({signal, _What, _From, _To}) ->
true;
switch_signal({signal, _What, _To}) ->
true;
switch_signal(_Else) ->
false.

is_valid_signal(Signal) -> switch_signal(Signal).

I know that the control structures are a little bit faster, not much, but I find that the function form is more readable.

Andrzej Sliwa

unread,
Aug 27, 2012, 5:00:14 PM8/27/12
to Jayson Barley, Erlang Questions
there is no difference in speed at all,

> switch_signal({signal, _What, _From, _To}) ->
> true;
> switch_signal({signal, _What, _To}) ->
> true;
> switch_signal(_Else) ->
> false.

is compiled internally to one function with case
so code speed is equal

there is only difference in readability.
> _______________________________________________
> erlang-questions mailing list
> erlang-q...@erlang.org
> http://erlang.org/mailman/listinfo/erlang-questions

_______________________________________________
erlang-questions mailing list
erlang-q...@erlang.org
http://erlang.org/mailman/listinfo/erlang-questions

Jayson Barley

unread,
Aug 27, 2012, 5:44:53 PM8/27/12
to Andrzej Sliwa, Erlang Questions
That is interesting. I didn't know that. I wonder why over 100 million iterations I consistently get between 1 to 2 second differences in timing. I know that there will be differences due to garbage collection, other processes running, etc, but the is_greater_than_control/2 function always outperforms the is_greater_than/2 function. Am I making a wrong assumption and eventually the opposite would be true? Could you test the below code on your system and let me know if you get similar results?

-module(test).
-compile(export_all).


is_true(true) ->
    true;
is_true(false) ->
    false.

is_greater_than(X, Y) ->
    is_true(X>Y).

is_greater_than_control(X, Y) ->

    if
        X>Y ->
            true;
        true -> % works as an 'else' branch
            false
    end.

test_if(X, X, _) ->
    done;
test_if(X, Max, Fun) ->
    Fun(X, Max),
    test_if(X + 1, Max, Fun).

test_if(Max) ->
    [timer:tc(test, test_if, [0, Max, fun is_greater_than/2]), timer:tc(test, test_if, [0, Max, fun is_greater_than_control/2])].

8> test:test_if(100000000).
[{7234000,done},{5718999,done}]

Paul Oliver

unread,
Aug 27, 2012, 6:35:58 PM8/27/12
to Jayson Barley, Erlang Questions
You can use erlc +to_core to get the core erlang:

module 'test' ['is_greater_than'/2,
      'is_greater_than_control'/2,
      'is_true'/1,
      'module_info'/0,
      'module_info'/1]
    attributes []
'is_true'/1 =
    %% Line 5
    fun (_cor0) ->
case _cor0 of
 <'true'> when 'true' ->
     %% Line 6
     'true'
 %% Line 7
 <'false'> when 'true' ->
     %% Line 8
     'false'
 ( <_cor1> when 'true' ->
( primop 'match_fail'
     ({'function_clause',_cor1})
 -| [{'function_name',{'is_true',1}}] )
   -| ['compiler_generated'] )
end
'is_greater_than'/2 =
    %% Line 10
    fun (_cor1,_cor0) ->
let <_cor2> =
   %% Line 11
   call 'erlang':'>'
(_cor1, _cor0)
in  %% Line 11
   apply 'is_true'/1
(_cor2)
'is_greater_than_control'/2 =
    %% Line 13
    fun (_cor1,_cor0) ->
%% Line 15
case <> of
 %% Line 16
 <>
     when call 'erlang':'>'
   (_cor1,
    _cor0) ->
     %% Line 17
     'true'
 %% Line 18
 <> when 'true' ->
     %% Line 19
     'false'
end
'module_info'/0 =
    fun () ->
call 'erlang':'get_module_info'
   ('test')
'module_info'/1 =
    fun (_cor0) ->
call 'erlang':'get_module_info'
   ('test', _cor0)
end

Jayson Barley

unread,
Aug 27, 2012, 6:47:27 PM8/27/12
to Paul Oliver, Erlang Questions
Thank you Paul. It appears to me that they don't compile down to the same code. Am I missing something?

Richard O'Keefe

unread,
Aug 27, 2012, 7:32:32 PM8/27/12
to Jayson Barley, Erlang Questions

On 28/08/2012, at 8:56 AM, Jayson Barley wrote:

> I am not sure I understand why we have them.


"if" is indeed a special case of "case",
and "case" is basically an in-lined function call.
And there is a hint: if you are going to inline
a function call, what are you going to inline it
*as*? The possibility must exist in the core
syntax, if nowhere else.

But "try" and "receive" couldn't be anything else.
Without "receive", we'd have to use channels.

> is_greater_than(X, Y) ->
> is_true(X>Y).

That would never have worked in the Good Old Days,
where X > Y was a guard test and not usable in an
ordinary expression.

> I know that the control structures are a little bit faster, not much, but I find that the function form is more readable.

You will find it hard to get a consensus on that.

Michael Richter

unread,
Aug 27, 2012, 10:50:40 PM8/27/12
to Jayson Barley, Erlang Questions
On 28 August 2012 04:56, Jayson Barley <jayson...@gmail.com> wrote:
I can do the same thing with case statements
is_valid_signal(Signal) ->
    case Signal of
        {signal, _What, _From, _To} ->
            true;
        {signal, _What, _To} ->
            true;
        _Else ->
            false
    end.
Becomes
switch_signal({signal, _What, _From, _To}) ->
true;
switch_signal({signal, _What, _To}) ->
true;
switch_signal(_Else) ->
false.

is_valid_signal(Signal) -> switch_signal(Signal).

I know that the control structures are a little bit faster, not much, but I find that the function form is more readable.

A case structure is like an anonymous function in this.  The use case for them is the same as the use case for funs.

You had to come up with, even in your trivial example, two module-global symbols: is_valid_signal/1 and switch_signal/1.  Take a non-trivial module, now, and multiply it by the number of public API functions.  You'll start getting name clashes as you go on.  For example, with your code above, what happens if I want to tell a device to change the signal it's sending?  I can't use switch_signal/1 any longer, so I have to use something like change_signal/1 instead.  Now I've got two functions, switch_signal/1 and change_signal/1, that look very similar by name.  I have to dig deeper to figure out which does what if I'm maintaining the code base.  You could argue that this is because switch_signal/1 is poorly named, but then it falls right back into why case structures are useful: they're anonymous.  Anonymous constructs (cases, funs, etc.) are quite often very useful.

Further, your version of is_valid_signal/1 has a larger cognitive load.  In the first case, if I want to know what is_valid_signal does and how it works, I look up one function.  All the information I need to understand the function is in one place.  With your solution this is now divided up into multiple places.  There's more work to decode what's going on.  This being a trivial function disguises the effect somewhat, but in a larger, less trivial function this can be an impediment, again, to understanding the code and reasoning about its behaviour.

--
"Perhaps people don't believe this, but throughout all of the discussions of entering China our focus has really been what's best for the Chinese people. It's not been about our revenue or profit or whatnot."
--Sergey Brin, demonstrating the emptiness of the "don't be evil" mantra.

Joe Armstrong

unread,
Aug 28, 2012, 7:12:30 AM8/28/12
to Jayson Barley, Erlang Questions
On Mon, Aug 27, 2012 at 10:56 PM, Jayson Barley <jayson...@gmail.com> wrote:
> I am not sure I understand why we have them. For instance I can take the
> following code
>
> is_greater_than(X, Y) ->
> if
> X>Y ->
> true;
> true -> % works as an 'else' branch
> false
> end.
>
> And make it
>
> is_true(true) ->
> true;
> is_true(false) ->
> false.

Or even

is_greater_than(X, Y) -> X > Y.

/J


> is_greater_than(X, Y) ->
> is_true(X>Y).
>
> I can do the same thing with case statements
>
> is_valid_signal(Signal) ->
> case Signal of
> {signal, _What, _From, _To} ->
> true;
> {signal, _What, _To} ->
> true;
> _Else ->
> false
> end.
>
> Becomes
>
> switch_signal({signal, _What, _From, _To}) ->
> true;
> switch_signal({signal, _What, _To}) ->
> true;
> switch_signal(_Else) ->
> false.
>
> is_valid_signal(Signal) ->
> switch_signal(Signal).
>
>
> I know that the control structures are a little bit faster, not much, but I
> find that the function form is more readable.
>

黃耀賢 (Yau-Hsien Huang)

unread,
Aug 28, 2012, 12:50:02 PM8/28/12
to Jayson Barley, erlang-q...@erlang.org
On Tue, Aug 28, 2012 at 4:56 AM, Jayson Barley <jayson...@gmail.com> wrote:
I am not sure I understand why we have them. For instance I can take the following code
is_greater_than(X, Y) ->
    if
        X>Y ->
            true;
        true -> % works as an 'else' branch
            false
    end.
And make it
is_true(true) ->
true;
is_true(false) ->
false.

is_greater_than(X, Y) -> is_true(X>Y).
Your is_true/1 means that it may meet either true, false, or other values.
However, 'if' statement means that it would meet two cases definitely.

is_true/1 is an identity function (fun(X) -> X end) working on Boolean values.
Then it may be is_true(X>Y) or is_true(is_true(X>Y)) or
is_true(is_true(is_true(is_true(is_true(is_true(X>Y)))))).
Eventually, It is (X > Y).
The above is an alternative version of
is_valid_signal({signal, _What, _From, _To}) ->
    true;
is_valid_signal({signal, _What, _To}) ->
true;
s_valid_signal(_Else) ->
false.
Don't repeat it.

 
I know that the control structures are a little bit faster, not much, but I find that the function form is more readable.

_______________________________________________
erlang-questions mailing list
erlang-q...@erlang.org
http://erlang.org/mailman/listinfo/erlang-questions



In my opinion, 'if' block limits the domain, and 'case' block provides
a way more writable than to write many function heads to pick its
matching patterns.


--

Best Regards.

--- Y-H. H.


Volodymyr Kyrychenko

unread,
Aug 28, 2012, 7:04:36 PM8/28/12
to Erlang Questions
Jayson Barley wrote:
> I am not sure I understand why we have them. For instance I can take the
> following code
>
> is_greater_than(X, Y) ->
> if
> X>Y ->
> true;
> true -> % works as an 'else' branch
> false
> end.
>
> And make it
>
> is_true(true) ->
> true;
> is_true(false) ->
> false.
>
> is_greater_than(X, Y) ->
> is_true(X>Y).

Because erlang has no call-by-name/need.

What you're proposing exists in Smalltalk.

x ifTrue: [ code ]

In erlang for this to work it should be like:

if_(X>Y, fun() -> do something end, fun() ->
this_is_else_for_something_to_return_if_not end).

For this to work without having to wrap everything into funs there
should be lazy evaluation order in the language.

--
Volodymyr Kyrychenko



signature.asc

Toby Thain

unread,
Aug 28, 2012, 7:36:14 PM8/28/12
to Erlang Questions
On 28/08/12 7:04 PM, Volodymyr Kyrychenko wrote:
> Jayson Barley wrote:
>> I am not sure I understand why we have them. For instance I can take the
>> following code
>>
>> is_greater_than(X, Y) ->
>> if
>> X>Y ->
>> true;
>> true -> % works as an 'else' branch
>> false
>> end.
>>
>> And make it
>>
>> is_true(true) ->
>> true;
>> is_true(false) ->
>> false.
>>
>> is_greater_than(X, Y) ->
>> is_true(X>Y).
>
> Because erlang has no call-by-name/need.
>
> What you're proposing exists in Smalltalk.
>
> x ifTrue: [ code ]

This lazy block passing also exists in Scala, which enables various
forms of ad-hoc control structures like:

spawn {
// code block to run in another thread
}

or

val f = future { /* some computation wanted later */ }

Other Scala features of interest to Erlangers are immutable bindings,
pattern matching, and of course actors.

--Toby

>
> In erlang for this to work it should be like:
>
> if_(X>Y, fun() -> do something end, fun() ->
> this_is_else_for_something_to_return_if_not end).
>
> For this to work without having to wrap everything into funs there
> should be lazy evaluation order in the language.
>
>
>
>

dmitry kolesnikov

unread,
Aug 28, 2012, 11:40:14 PM8/28/12
to Toby Thain, Erlang Questions
Hello,

Did I missed something but given Scala constructions has equivalents
in Erlang? See below

Regards,
Dmitry
In Erlang
spawn(
fun()->
// code block to run
end
)


> or
>
> val f = future { /* some computation wanted later */ }
>
F = fun() ->
// some computation
end

Richard O'Keefe

unread,
Aug 29, 2012, 12:56:20 AM8/29/12
to dmitry kolesnikov, Erlang Questions

On 29/08/2012, at 3:40 PM, dmitry kolesnikov wrote:
> Did I missed something but given Scala constructions has equivalents
> in Erlang? See below

You missed two things: (a) brevity and (b) a future is not the same
thing as a closure -- it gets evaluated just once.

One of the core strengths of Erlang is the things it *cannot* do.
Reply all
Reply to author
Forward
0 new messages