Advanced Erlang Subtleties

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Advanced Erlang Subtleties

James Churchman
Mondays's excellent London User Group about Misultin ( by Roberto Ostinelli ) brought up many fascinating discussions, one was about supervisors vs custom rolling the functionality & storing Pids in an Ets table due to supervisors storing children as a list, how to make Misultin 0.8 OTP compatible but still quick to get started with & directly embeddable etc..

One thing I decided to after one convocation was to document all the erlang syntax ( eg, no otp ) subtleties or bits i find counter-intuitive i have discovered, for the use of others & also to prompt the occasional "why was it implemented this way" out of anybody who knows. None of this is a criticism ( and some is point out things that are lesser used and i very much like), as so far i have found erlang to have very few inconsistencies,including in error conditions!, but knowing all the subtleties for any language I use is a personal favourite of mine ( coming from js gives you that! )

so here we go :

*A) List Comprehension are personally my favourite part of erlang, but have the most subtleties of anything, partially because they pack a lot of functionality in a tiny bit of syntax :

1) LC's act as a list filter, then list map
therefore :
List = [{a,b},{c,d,e,f},{h,i}]
[I1||{I1,I2}<-List] will not crash but instead filter out the 4 elem tuple. This is great, but where is the opposite version of a LC that is only a map and will crash ?
The standard behaviour seems to go against "let it crash" & on a few occasions have had code that gives a final [] rather than the output expected.
Using :
[fun({I1,I2}) -> I1 end(Elem)||Elem<-List]
or 
[begin {I1,I2}=Elem, I1 end||Elem<-List]
give a behaviour equivalent to a list map but are less elegant than a :
[I1||{I1,I2}<---List] or something similar. ( notice the <--- )
I would really welcome this addition to the language ( and many many many user i have met presume this is the default behaviour for a LC :-)

2) List comprehensions can't normally contain multiple statements, possibly because it might confuse users that [do(),do2(),do3()||_<-List] would add three elements to a list each time (that syntax could be really useful to do that tho!). You can however use the "begin end", also shown above, syntax like : [begin do(),do2(),do3() end ||_<-List] to make it all "one statement"
(and you will end up with a list of the results of do3()).

3) This, i feel, is a VERY annoying scoping bug :

OuterVar = 'a', List = [{a,b},{c,d},{e,f}],

Y : [I2||{OuterVar,I2}<-List]

Z : [I2||{I1,I2}<-List when I1==OuterVar]

these do not produce the same result.

in "Y:" the "OuterVar" gets overridden, not matched, so you end up with every element matching. In "Z: " it behaves (as "Y:" should) ad filters out all but the matches with 'a'
this is unlike a case where
case input of Input -> ok; _ -> other end,
will give ok but if you set Input before to be say 'not_an_input_atom' it will give 'other'

I hate this behaviour... :-)

4) you can produce generatorless LC's
these act like a "if the entire list does not match the guards replace with an empty list otherwise return it".. they look crazy! but can be useful when generating iolists (or *not* iolists :-) to avoid case statements

5) custom guards and error handling :
after much thought i feel this is the correct behaviour, though it's surprising :

List = [1,2,three,4,5],

Y : [I1||I1<-List when I1+1>0 ]

Z : Guard = fun(I1) -> I1+1>0 end,
[I1||I1<-List when Guard(I1)]

"Y :" will filter out 'three' and "Z : " will crash, and importantly crashes when it hits 'three' and not before. same code! however having guards that rerun false rather than crash prevents complex is_type() etc.. whereas, when you call you could be calling huge functions, even with side effects ; silencing all that makes no sense. It also allows you to make guards that crash, so make the code a tad less defensive.

6) list comprehensions crash with an invalid list, but only when it reaches the tail element :
[io:format("~p~n",[Elem])||Elem<-[1,2,3,4|invalid_tail]]
will first print out 1 to 4, then crash


*B) variable name repetition matchings :

you can write :
function_name(Elem1,Elem1)->this_matches;
function_name(Elem1,Elem2)->this_does_not_match;

and when you put function_name(100,100) it will match. this may seem obvious but in many cases it can reduce a lot of code where otherwise you are writing guards or dropping into a case, especially when you have more complex disassembly pattern matches combined with recursion. I use this in much of my code, as it feels more functional than lots of == or case etc.. :-) 


*C) pattern matches can be anywhere
All things in erlang always return, even pattern matches which return themselves so :

[elem1,Var1 = elem2,elem3] will give the list [elem1,elem2,elem3] but also set Var1 to elem2. In a more complex case this can (with reasonable use) reduce code a lot as it lets you change the order you build up code, so the last element that returns is the one you want.. you don't need to build up an intermediate value, do some code, then place that var at the end to return.

They can also be used in function heads like :
function(Elem1={_,_,_})-> Elem.

*D) "begin end" statements can be anywhere

this can be used to embed more than one statement when building a list etc :
[elem1,
elem2,
begin
{Elem3,_,_,_ } = fetch_elem3(),
Elem3
end,
elem4]

its also possible to put case, if etc.. ( as everything in erlang returns) statements there too like :
[elem1,
elem2,
case Elem3 of
_ when is_atom(Elem3) -> atom_to_list(Elem3)
_ -> Elem3
end,
elem4]

*E) case ( and other like try catch etc.. ) drop their matched variables out the bottom ( eg they stay in scope even after they are used)
case input_atom of Input -> ok; _ -> other end,
Input is now still 'input_atom'

*F) Operators have Type Precedence
"hello < 10" etc.. does not throw but returns false.
the atom 'aaa' is less than the atom 'aab' etc..

There may be small use cases for this like implementing generic sorting functions etc.. but 99% of the time if you magnitude compare two different types ( without intermediate type conversion, and ignoring float/int differences) this is a huge bug in your code. 

I can see the equivalent evaluation of "250"<250 being a common mistake, and always being false is hardly useful. Also this behaviour can hardly help the dialyser spot common type mistakes as this is valid code. Does this ever prove to be a big problem in large scale systems? or not really? or does producing a module full of :

'>'(I1,I2) when is_number(I1), is_number(I2) -> I1>I2. ( '>' could be "greater_than" or "gt" or similar)
which crashes on non numeric input produce helpful crashes at the point of error? or just overkill?

*G) {module,function}(Inputs)
The tuple pair can be used as a way of calling functions, tho the recommended style is: fun mod:funct/arity but still fun to know.

*H) Funs can be self executing by wrapping () after them like : fun (FunInput) -> some_code() end(Input)

*J) try/catch: 
1) - in the ERROR_TYPE:ERROR catch the throw: can be left out, and it means the same as catching a throw of type "Error".. nice short hand.
There is also a 3rd parameter in the AST that is always a match all ( a bit like ERROR_TYPE:ERROR:_ ) does anybody know what this means? is there an extra option in a catch?

( maybe everybody knows these last few but just to be sure )
2) if nothing matches in the catch statements the error propagates up as if there was no try catch at all, very useful and stops the need for re-throwing like other languages!
3) in the try Code of ... catch the "of" is optional & returns the CODE value if left out, also ( maybe v obvious) the code in the "of" section is not caught
4) the old, strange and broken "catch" requires wrapping in parens if you want its return value:
Var = catch throw(100). seems to be a syntax error
Var = (catch throw(100)). is fine
also distinguishing between certain throws and an ordinary return can be impossible.

*K) records
( this got too long so I made it a follow on email :-) basically I wish to write a (better) parse transform fix )

If after corrections/some extra suggestions someone wants to put this on their blog then feel free, I don't have one :-)
James

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Advanced Erlang Subtleties

Joe Armstrong-2


On Thu, May 19, 2011 at 10:02 PM, James Churchman <[hidden email]> wrote:
Mondays's excellent London User Group about Misultin ( by Roberto Ostinelli ) brought up many fascinating discussions, one was about supervisors vs custom rolling the functionality & storing Pids in an Ets table due to supervisors storing children as a list, how to make Misultin 0.8 OTP compatible but still quick to get started with & directly embeddable etc..

One thing I decided to after one convocation was to document all the erlang syntax ( eg, no otp ) subtleties or bits i find counter-intuitive i have discovered, for the use of others & also to prompt the occasional "why was it implemented this way" out of anybody who knows. None of this is a criticism ( and some is point out things that are lesser used and i very much like), as so far i have found erlang to have very few inconsistencies,including in error conditions!, but knowing all the subtleties for any language I use is a personal favourite of mine ( coming from js gives you that! )

so here we go :

*A) List Comprehension are personally my favourite part of erlang, but have the most subtleties of anything, partially because they pack a lot of functionality in a tiny bit of syntax :

1) LC's act as a list filter, then list map
therefore :
List = [{a,b},{c,d,e,f},{h,i}]
[I1||{I1,I2}<-List] will not crash but instead filter out the 4 elem tuple. This is great, but where is the opposite version of a LC that is only a map and will crash ?

lists:map(fun({I,J}) -> I end, List)

it is "only a map"

:-)


 
The standard behaviour seems to go against "let it crash" & on a few occasions have had code that gives a final [] rather than the output expected.
Using :
[fun({I1,I2}) -> I1 end(Elem)||Elem<-List]

But this does crash and does not produce []

1> L = [{1,2},{3,4},{1,2,3,4}].
[{1,2},{3,4},{1,2,3,4}]
2> [fun({I,J})->I end(E) || E <- L].
** exception error: no function clause matching 
                    erl_eval:'-inside-an-interpreted-fun-'({1,2,3,4})


same in compiled code - I checked

 
or 
[begin {I1,I2}=Elem, I1 end||Elem<-List]
give a behaviour equivalent to a list map but are less elegant than a :
[I1||{I1,I2}<---List] or something similar. ( notice the <--- )
I would really welcome this addition to the language ( and many many many user i have met presume this is the default behaviour for a LC :-)

2) List comprehensions can't normally contain multiple statements, possibly because it might confuse users that [do(),do2(),do3()||_<-List] would add three elements to a list each time (that syntax could be really useful to do that tho!). You can however use the "begin end", also shown above, syntax like : [begin do(),do2(),do3() end ||_<-List] to make it all "one statement"
(and you will end up with a list of the results of do3()).

It's a grammar thing. It's extremely difficult (impossible?) to
write a grammar where you can put a sequences of forms
at any place where a single form is expected - this is what
begin .. end is for it's like the lisp (progn a b c) which evaluates
a b and c in order and whose result is c.
 

3) This, i feel, is a VERY annoying scoping bug :

OuterVar = 'a', List = [{a,b},{c,d},{e,f}],

Y : [I2||{OuterVar,I2}<-List]

Z : [I2||{I1,I2}<-List when I1==OuterVar]

these do not produce the same result.


It's a feature :-)
 
in "Y:" the "OuterVar" gets overridden, not matched, so you end up with every element matching. In "Z: " it behaves (as "Y:" should) ad filters out all but the matches with 'a'
this is unlike a case where
case input of Input -> ok; _ -> other end,
will give ok but if you set Input before to be say 'not_an_input_atom' it will give 'other'

I hate this behaviour... :-)

4) you can produce generatorless LC's
these act like a "if the entire list does not match the guards replace with an empty list otherwise return it".. they look crazy! but can be useful when generating iolists (or *not* iolists :-) to avoid case statements

5) custom guards and error handling :
after much thought i feel this is the correct behaviour, though it's surprising :

List = [1,2,three,4,5],

Y : [I1||I1<-List when I1+1>0 ]

Z : Guard = fun(I1) -> I1+1>0 end,
[I1||I1<-List when Guard(I1)]

"Y :" will filter out 'three' and "Z : " will crash, and importantly crashes when it hits 'three' and not before. same code! however having guards that rerun false rather than crash prevents complex is_type() etc.. whereas, when you call you could be calling huge functions, even with side effects ; silencing all that makes no sense. It also allows you to make guards that crash, so make the code a tad less defensive.

6) list comprehensions crash with an invalid list, but only when it reaches the tail element :
[io:format("~p~n",[Elem])||Elem<-[1,2,3,4|invalid_tail]]
will first print out 1 to 4, then crash


*B) variable name repetition matchings :

you can write :
function_name(Elem1,Elem1)->this_matches;
function_name(Elem1,Elem2)->this_does_not_match;

and when you put function_name(100,100) it will match. this may seem obvious but in many cases it can reduce a lot of code where otherwise you are writing guards or dropping into a case, especially when you have more complex disassembly pattern matches combined with recursion. I use this in much of my code, as it feels more functional than lots of == or case etc.. :-) 


*C) pattern matches can be anywhere
All things in erlang always return, even pattern matches which return themselves so :

[elem1,Var1 = elem2,elem3] will give the list [elem1,elem2,elem3] but also set Var1 to elem2. In a more complex case this can (with reasonable use) reduce code a lot as it lets you change the order you build up code, so the last element that returns is the one you want.. you don't need to build up an intermediate value, do some code, then place that var at the end to return.

They can also be used in function heads like :
function(Elem1={_,_,_})-> Elem.

This is very useful I use this all the time

Suppose I want to do this

 foo({a,{b,X},c}) -> bar({b,X})

Better is

foo({a,{b,X}=Z,c}) -> bar(Z).


which avoids reconstructing the {b,X} term and is 2 characters shorter
and reduces the possibility of error if the subterm is large
 

*D) "begin end" statements can be anywhere

this can be used to embed more than one statement when building a list etc :
[elem1,
elem2,
begin
{Elem3,_,_,_ } = fetch_elem3(),
Elem3
end,
elem4]

its also possible to put case, if etc.. ( as everything in erlang returns) statements there too like :
[elem1,
elem2,
case Elem3 of
_ when is_atom(Elem3) -> atom_to_list(Elem3)
_ -> Elem3
end,
elem4]

*E) case ( and other like try catch etc.. ) drop their matched variables out the bottom ( eg they stay in scope even after they are used)
case input_atom of Input -> ok; _ -> other end,
Input is now still 'input_atom'

*F) Operators have Type Precedence
"hello < 10" etc.. does not throw but returns false.
the atom 'aaa' is less than the atom 'aab' etc..

There may be small use cases for this like implementing generic sorting functions etc.. but 99% of the time if you magnitude compare two different types ( without intermediate type conversion, and ignoring float/int differences) this is a huge bug in your code. 

Disagree - having a total order over all terms means it is
possible to write extremely useful generic functions. Key-Value
stores for example (llke dicts and ets tables) would be
very difficult to implement without this
 

I can see the equivalent evaluation of "250"<250 being a common mistake, and always being false is hardly useful.

A "<" B when A and B have different types means A is
"less complex" than B. Lists and more complex than integers
"250" is a list so "250"<250 is false.

Suppose I want to make a Key-Value database, where the Key can be anything, including 250 and "250" to make any form of ordered tree we need a total order over all the keys.
 
 
Also this behaviour can hardly help the dialyser spot common type mistakes as this is valid code. Does this ever prove to be a big problem in large scale systems? or not really? or does producing a module full of :

'>'(I1,I2) when is_number(I1), is_number(I2) -> I1>I2. ( '>' could be "greater_than" or "gt" or similar)
which crashes on non numeric input produce helpful crashes at the point of error? or just overkill?

*G) {module,function}(Inputs)
The tuple pair can be used as a way of calling functions, tho the recommended style is: fun mod:funct/arity but still fun to know.

*H) Funs can be self executing by wrapping () after them like : fun (FunInput) -> some_code() end(Input)

*J) try/catch: 
1) - in the ERROR_TYPE:ERROR catch the throw: can be left out, and it means the same as catching a throw of type "Error".. nice short hand.
There is also a 3rd parameter in the AST that is always a match all ( a bit like ERROR_TYPE:ERROR:_ ) does anybody know what this means? is there an extra option in a catch?

( maybe everybody knows these last few but just to be sure )
2) if nothing matches in the catch statements the error propagates up as if there was no try catch at all, very useful and stops the need for re-throwing like other languages!
3) in the try Code of ... catch the "of" is optional & returns the CODE value if left out, also ( maybe v obvious) the code in the "of" section is not caught
4) the old, strange and broken "catch" requires wrapping in parens if you want its return value:
Var = catch throw(100). seems to be a syntax error
Var = (catch throw(100)). is fine
also distinguishing between certain throws and an ordinary return can be impossible.

*K) records
( this got too long so I made it a follow on email :-) basically I wish to write a (better) parse transform fix )

If after corrections/some extra suggestions someone wants to put this on their blog then feel free, I don't have one :-)
James

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions



_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Advanced Erlang Subtleties

James Churchman
But this does crash and does not produce []

1> L = [{1,2},{3,4},{1,2,3,4}].
[{1,2},{3,4},{1,2,3,4}]
2> [fun({I,J})->I end(E) || E <- L].
** exception error: no function clause matching 
                    erl_eval:'-inside-an-interpreted-fun-'({1,2,3,4})


same in compiled code - I checked

yes that's what i ment ( sorry if i was unclear), you have to place a fun or a "begin match end" in to make it crash :-)
but it would be nice to have a "map" equivalent to LC's that had a small ( but cear ) syntax difference.  ( when i worry about this i tend to [begin {I1,I2}=Elem, I1 end||Elem<-List] rather than use list map or self executing fun .. :-)
 
It's a grammar thing. It's extremely difficult (impossible?) to
write a grammar where you can put a sequences of forms
at any place where a single form is expected - this is what
begin .. end is for it's like the lisp (progn a b c) which evaluates
a b and c in order and whose result is c.
 
i see v interesting!
not a problem tho as an extra begin end fixes it!
 
3) This, i feel, is a VERY annoying scoping bug :

OuterVar = 'a', List = [{a,b},{c,d},{e,f}],

Y : [I2||{OuterVar,I2}<-List]

Z : [I2||{I1,I2}<-List when I1==OuterVar]

these do not produce the same result.

It's a feature :-)
 
really?? it seems totally inconstant to the rest of erlang to me .. tho il take your word for it. it just annoys me especially as the vars in the guards even get effected by this "overwriting" behaviour. I guess its just my personal style that shows it up.

This is very useful I use this all the time

Suppose I want to do this

 foo({a,{b,X},c}) -> bar({b,X})

Better is

foo({a,{b,X}=Z,c}) -> bar(Z).


which avoids reconstructing the {b,X} term and is 2 characters shorter
and reduces the possibility of error if the subterm is large

i agree entirely:-)... problem with email is its hard to convey what you mean... i like this a huge amount!
fair bit of erlang code i see does not  "= p-match" in the function head when it could.. 
- Show quoted text -
Thats a very good point that I had not thought of the hash maps case... it still leads to errors tho and makes erlang feel a bit like a loosely typed language in that regard. My question i guess was more have other ppl hit this silent error (which can be hard to trace) or have large products produced numeric only comparison modules? if you start off with say xml etc.. its easy to forget a list_to_integer() and the comparisons don't throw you just get weird results!

The main point of the email was i had never seen a detailed list of all the less known syntax features and thought it would interest the list :-) most of the above are great features ( apart from the lack of map LC's :-) ) so i guess many thanks for that joe!

james

On 19 May 2011 22:02, Joe Armstrong <[hidden email]> wrote:


On Thu, May 19, 2011 at 10:02 PM, James Churchman <[hidden email]> wrote:
Mondays's excellent London User Group about Misultin ( by Roberto Ostinelli ) brought up many fascinating discussions, one was about supervisors vs custom rolling the functionality & storing Pids in an Ets table due to supervisors storing children as a list, how to make Misultin 0.8 OTP compatible but still quick to get started with & directly embeddable etc..

One thing I decided to after one convocation was to document all the erlang syntax ( eg, no otp ) subtleties or bits i find counter-intuitive i have discovered, for the use of others & also to prompt the occasional "why was it implemented this way" out of anybody who knows. None of this is a criticism ( and some is point out things that are lesser used and i very much like), as so far i have found erlang to have very few inconsistencies,including in error conditions!, but knowing all the subtleties for any language I use is a personal favourite of mine ( coming from js gives you that! )

so here we go :

*A) List Comprehension are personally my favourite part of erlang, but have the most subtleties of anything, partially because they pack a lot of functionality in a tiny bit of syntax :

1) LC's act as a list filter, then list map
therefore :
List = [{a,b},{c,d,e,f},{h,i}]
[I1||{I1,I2}<-List] will not crash but instead filter out the 4 elem tuple. This is great, but where is the opposite version of a LC that is only a map and will crash ?

lists:map(fun({I,J}) -> I end, List)

it is "only a map"

:-)


 
The standard behaviour seems to go against "let it crash" & on a few occasions have had code that gives a final [] rather than the output expected.
Using :
[fun({I1,I2}) -> I1 end(Elem)||Elem<-List]

But this does crash and does not produce []

1> L = [{1,2},{3,4},{1,2,3,4}].
[{1,2},{3,4},{1,2,3,4}]
2> [fun({I,J})->I end(E) || E <- L].
** exception error: no function clause matching 
                    erl_eval:'-inside-an-interpreted-fun-'({1,2,3,4})


same in compiled code - I checked

 
or 
[begin {I1,I2}=Elem, I1 end||Elem<-List]
give a behaviour equivalent to a list map but are less elegant than a :
[I1||{I1,I2}<---List] or something similar. ( notice the <--- )
I would really welcome this addition to the language ( and many many many user i have met presume this is the default behaviour for a LC :-)

2) List comprehensions can't normally contain multiple statements, possibly because it might confuse users that [do(),do2(),do3()||_<-List] would add three elements to a list each time (that syntax could be really useful to do that tho!). You can however use the "begin end", also shown above, syntax like : [begin do(),do2(),do3() end ||_<-List] to make it all "one statement"
(and you will end up with a list of the results of do3()).

It's a grammar thing. It's extremely difficult (impossible?) to
write a grammar where you can put a sequences of forms
at any place where a single form is expected - this is what
begin .. end is for it's like the lisp (progn a b c) which evaluates
a b and c in order and whose result is c.
 

3) This, i feel, is a VERY annoying scoping bug :

OuterVar = 'a', List = [{a,b},{c,d},{e,f}],

Y : [I2||{OuterVar,I2}<-List]

Z : [I2||{I1,I2}<-List when I1==OuterVar]

these do not produce the same result.


It's a feature :-)
 
in "Y:" the "OuterVar" gets overridden, not matched, so you end up with every element matching. In "Z: " it behaves (as "Y:" should) ad filters out all but the matches with 'a'
this is unlike a case where
case input of Input -> ok; _ -> other end,
will give ok but if you set Input before to be say 'not_an_input_atom' it will give 'other'

I hate this behaviour... :-)

4) you can produce generatorless LC's
these act like a "if the entire list does not match the guards replace with an empty list otherwise return it".. they look crazy! but can be useful when generating iolists (or *not* iolists :-) to avoid case statements

5) custom guards and error handling :
after much thought i feel this is the correct behaviour, though it's surprising :

List = [1,2,three,4,5],

Y : [I1||I1<-List when I1+1>0 ]

Z : Guard = fun(I1) -> I1+1>0 end,
[I1||I1<-List when Guard(I1)]

"Y :" will filter out 'three' and "Z : " will crash, and importantly crashes when it hits 'three' and not before. same code! however having guards that rerun false rather than crash prevents complex is_type() etc.. whereas, when you call you could be calling huge functions, even with side effects ; silencing all that makes no sense. It also allows you to make guards that crash, so make the code a tad less defensive.

6) list comprehensions crash with an invalid list, but only when it reaches the tail element :
[io:format("~p~n",[Elem])||Elem<-[1,2,3,4|invalid_tail]]
will first print out 1 to 4, then crash


*B) variable name repetition matchings :

you can write :
function_name(Elem1,Elem1)->this_matches;
function_name(Elem1,Elem2)->this_does_not_match;

and when you put function_name(100,100) it will match. this may seem obvious but in many cases it can reduce a lot of code where otherwise you are writing guards or dropping into a case, especially when you have more complex disassembly pattern matches combined with recursion. I use this in much of my code, as it feels more functional than lots of == or case etc.. :-) 


*C) pattern matches can be anywhere
All things in erlang always return, even pattern matches which return themselves so :

[elem1,Var1 = elem2,elem3] will give the list [elem1,elem2,elem3] but also set Var1 to elem2. In a more complex case this can (with reasonable use) reduce code a lot as it lets you change the order you build up code, so the last element that returns is the one you want.. you don't need to build up an intermediate value, do some code, then place that var at the end to return.

They can also be used in function heads like :
function(Elem1={_,_,_})-> Elem.

This is very useful I use this all the time

Suppose I want to do this

 foo({a,{b,X},c}) -> bar({b,X})

Better is

foo({a,{b,X}=Z,c}) -> bar(Z).


which avoids reconstructing the {b,X} term and is 2 characters shorter
and reduces the possibility of error if the subterm is large
 

*D) "begin end" statements can be anywhere

this can be used to embed more than one statement when building a list etc :
[elem1,
elem2,
begin
{Elem3,_,_,_ } = fetch_elem3(),
Elem3
end,
elem4]

its also possible to put case, if etc.. ( as everything in erlang returns) statements there too like :
[elem1,
elem2,
case Elem3 of
_ when is_atom(Elem3) -> atom_to_list(Elem3)
_ -> Elem3
end,
elem4]

*E) case ( and other like try catch etc.. ) drop their matched variables out the bottom ( eg they stay in scope even after they are used)
case input_atom of Input -> ok; _ -> other end,
Input is now still 'input_atom'

*F) Operators have Type Precedence
"hello < 10" etc.. does not throw but returns false.
the atom 'aaa' is less than the atom 'aab' etc..

There may be small use cases for this like implementing generic sorting functions etc.. but 99% of the time if you magnitude compare two different types ( without intermediate type conversion, and ignoring float/int differences) this is a huge bug in your code. 

Disagree - having a total order over all terms means it is
possible to write extremely useful generic functions. Key-Value
stores for example (llke dicts and ets tables) would be
very difficult to implement without this
 

I can see the equivalent evaluation of "250"<250 being a common mistake, and always being false is hardly useful.

A "<" B when A and B have different types means A is
"less complex" than B. Lists and more complex than integers
"250" is a list so "250"<250 is false.

Suppose I want to make a Key-Value database, where the Key can be anything, including 250 and "250" to make any form of ordered tree we need a total order over all the keys.
 
 
Also this behaviour can hardly help the dialyser spot common type mistakes as this is valid code. Does this ever prove to be a big problem in large scale systems? or not really? or does producing a module full of :

'>'(I1,I2) when is_number(I1), is_number(I2) -> I1>I2. ( '>' could be "greater_than" or "gt" or similar)
which crashes on non numeric input produce helpful crashes at the point of error? or just overkill?

*G) {module,function}(Inputs)
The tuple pair can be used as a way of calling functions, tho the recommended style is: fun mod:funct/arity but still fun to know.

*H) Funs can be self executing by wrapping () after them like : fun (FunInput) -> some_code() end(Input)

*J) try/catch: 
1) - in the ERROR_TYPE:ERROR catch the throw: can be left out, and it means the same as catching a throw of type "Error".. nice short hand.
There is also a 3rd parameter in the AST that is always a match all ( a bit like ERROR_TYPE:ERROR:_ ) does anybody know what this means? is there an extra option in a catch?

( maybe everybody knows these last few but just to be sure )
2) if nothing matches in the catch statements the error propagates up as if there was no try catch at all, very useful and stops the need for re-throwing like other languages!
3) in the try Code of ... catch the "of" is optional & returns the CODE value if left out, also ( maybe v obvious) the code in the "of" section is not caught
4) the old, strange and broken "catch" requires wrapping in parens if you want its return value:
Var = catch throw(100). seems to be a syntax error
Var = (catch throw(100)). is fine
also distinguishing between certain throws and an ordinary return can be impossible.

*K) records
( this got too long so I made it a follow on email :-) basically I wish to write a (better) parse transform fix )

If after corrections/some extra suggestions someone wants to put this on their blog then feel free, I don't have one :-)
James

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions




_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Advanced Erlang Subtleties

Jeff Schultz
In reply to this post by Joe Armstrong-2
On Thu, May 19, 2011 at 11:02:35PM +0200, Joe Armstrong wrote:
> > I can see the equivalent evaluation of "250"<250 being a common mistake,
> > and always being false is hardly useful.

> A "<" B when A and B have different types means A is
> "less complex" than B. Lists and more complex than integers
> "250" is a list so "250"<250 is false.

> Suppose I want to make a Key-Value database, where the Key can be anything,
> including 250 and "250" to make any form of ordered tree we need a total
> order over all the keys.

Indeed.  If we can have only one built-in comparison, it needs to be
total over all values.  It is a pity that Prolog's distinction between
term comparison and number comparison wasn't carried over to Erlang :-(


    Jeff Schultz
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Advanced Erlang Subtleties

James Churchman

Indeed.  If we can have only one built-in comparison, it needs to be
total over all values.  It is a pity that Prolog's distinction between
term comparison and number comparison wasn't carried over to Erlang :-(

Indeed that would have made the language a tad bigger, but stopped this ambiguity!

Are there any other  good subtleties to erlang that anybody wishes to add to this list?
I can't have covered all of them :-)

also does anybody know the answer to my :
There is a 3rd parameter in the AST for a catch section in a Try/Catch that is always a match all ( a bit like ERROR_TYPE:ERROR:_ ) does anybody know what this means? is there an extra option in a catch that is rarely used?

James

On 20 May 2011 04:45, Jeff Schultz <[hidden email]> wrote:
On Thu, May 19, 2011 at 11:02:35PM +0200, Joe Armstrong wrote:
> > I can see the equivalent evaluation of "250"<250 being a common mistake,
> > and always being false is hardly useful.

> A "<" B when A and B have different types means A is
> "less complex" than B. Lists and more complex than integers
> "250" is a list so "250"<250 is false.

> Suppose I want to make a Key-Value database, where the Key can be anything,
> including 250 and "250" to make any form of ordered tree we need a total
> order over all the keys.

Indeed.  If we can have only one built-in comparison, it needs to be
total over all values.  It is a pity that Prolog's distinction between
term comparison and number comparison wasn't carried over to Erlang :-(


   Jeff Schultz
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Advanced Erlang Subtleties

Richard Carlsson-3
On 05/20/2011 01:35 PM, James Churchman wrote:
> also does anybody know the answer to my :
>>  There is a 3rd parameter in the AST for a catch section in a Try/Catch
> that is always a match all ( a bit like ERROR_TYPE:ERROR:_ ) does
> anybody know what this means? is there an extra option in a catch that
> is rarely used?

Yes, it's a cheap and compact encoding of the stack trace (as a bignum
or binary, I don't remember). This is so that no time is wasted building
a symbolic stack trace as a list of tuples that might simply get
discarded as soon as the exception is caught, while making it possible
to suspend an exception temporarily, drop back into Erlang code to match
against a set of catch-patterns, and re-throw the whole thing again if
nothing matched. If it's needed for anything, the blob will be unpacked
into a symbolic trace.

All in all, you're better off pretending you don't know anything about
this, apart from the fact that effort has been made to keep exceptions
as cheap as possible.

     /Richard
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Advanced Erlang Subtleties

James Churchman
Very interesting and makes a lot of sense.... i was hoping that 'better off pretending you don't know anything about this' was the case and there was not some huge set of optional extra stuff onto of a try catch!

On 20 May 2011, at 13:34, Richard Carlsson wrote:

> On 05/20/2011 01:35 PM, James Churchman wrote:
>> also does anybody know the answer to my :
>>> There is a 3rd parameter in the AST for a catch section in a Try/Catch
>> that is always a match all ( a bit like ERROR_TYPE:ERROR:_ ) does
>> anybody know what this means? is there an extra option in a catch that
>> is rarely used?
>
> Yes, it's a cheap and compact encoding of the stack trace (as a bignum or binary, I don't remember). This is so that no time is wasted building a symbolic stack trace as a list of tuples that might simply get discarded as soon as the exception is caught, while making it possible to suspend an exception temporarily, drop back into Erlang code to match against a set of catch-patterns, and re-throw the whole thing again if nothing matched. If it's needed for anything, the blob will be unpacked into a symbolic trace.
>
> All in all, you're better off pretending you don't know anything about this, apart from the fact that effort has been made to keep exceptions as cheap as possible.
>
>    /Richard

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Advanced Erlang Subtleties

Joe Armstrong-2
In reply to this post by Jeff Schultz


On Fri, May 20, 2011 at 5:45 AM, Jeff Schultz <[hidden email]> wrote:
On Thu, May 19, 2011 at 11:02:35PM +0200, Joe Armstrong wrote:
> > I can see the equivalent evaluation of "250"<250 being a common mistake,
> > and always being false is hardly useful.

> A "<" B when A and B have different types means A is
> "less complex" than B. Lists and more complex than integers
> "250" is a list so "250"<250 is false.

Another thought I had about this was that in some languages
"10" < 20 might be true, and "20" < 10 or 20 < "10" might be false.

In Erlang a string (list) is always bigger (ie more complex) than an integer.

A Javascript programmer might get confused here.

Erlang:

 "12" + 1   => (exception)
 "12" - 1    => (exception)
 "12" > 2    => true
 "12" > 15  => true

Javascript:

"12" + 1 => "121"
"12" - 1  => 11
"12" > 2 => true
"12" > 15 => false

Overloading plus to mean either plus or string concatenation is
horrible - and shouldn't "-" mean string subtraction (sometimes)

so "123" - 23 =>100 and "abc" - "a" => "bc" and "abc" - "c" => "ab"
:-)

I prefer the Erlang method - but then I'm probably somewhat biased here...

This (Javascript) horrible way of doing things caused me
hours of debugging - since my debugger wrote strings without
the quotes so I couldn't see the difference between "123" and 123
when printed.

Javascript also crashes late, and variables in closures 
can be changed after binding them into the closures ... and the
concurrency model is ... (isn't) - but it's fun to muck around with
and do things in the browser.


> Suppose I want to make a Key-Value database, where the Key can be anything,
> including 250 and "250" to make any form of ordered tree we need a total
> order over all the keys.

Indeed.  If we can have only one built-in comparison, it needs to be
total over all values.  It is a pity that Prolog's distinction between
term comparison and number comparison wasn't carried over to Erlang :-(


   Jeff Schultz
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Advanced Erlang Subtleties

Ulf Wiger

On 20 May 2011, at 18:23, Joe Armstrong wrote:

> Javascript:
>
> "12" + 1 => "121"
> "12" - 1  => 11

Aah, that's cute. :)

A nice example of how semantics is so much more important than syntax…

BR,
Ulf

Ulf Wiger, CTO, Erlang Solutions, Ltd.
http://erlang-solutions.com



_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Advanced Erlang Subtleties

James Churchman
In reply to this post by Joe Armstrong-2
yes this is where i think erlang really gets the balance correct, being dynamically typed ( so far less repetitive defining types where the type is either obvious or does not matter and is just begin "messaged" threw .. tho i know other people are strongly in favour of static typing)  but not Loosely Typed so avoids the huge amounts of ambiguity like what happens when you "add" or "subtract" different types as does JS, leading to the bug showing up a huge distance way from the code that was at fault.. unfortunately JS also has hundreds of faults & bugs ( around 270 at my count ;-) ) in the original implementation that due to a big of a chicken / egg scenario cant really ever be changed :-) things like "crashing late" come from the fact that early versions had no error handling at all! ( eg no try catch )

>A nice example of how semantics is so much more important than syntax…

yes and shows that purely putting new syntax's onto an existing language only aid developer productivity & readability a bit and generally don't make the code (much) more reliable

On Fri, May 20, 2011 at 5:45 AM, Jeff Schultz <[hidden email]> wrote:
On Thu, May 19, 2011 at 11:02:35PM +0200, Joe Armstrong wrote:
> > I can see the equivalent evaluation of "250"<250 being a common mistake,
> > and always being false is hardly useful.

> A "<" B when A and B have different types means A is
> "less complex" than B. Lists and more complex than integers
> "250" is a list so "250"<250 is false.

Another thought I had about this was that in some languages
"10" < 20 might be true, and "20" < 10 or 20 < "10" might be false.

In Erlang a string (list) is always bigger (ie more complex) than an integer.

A Javascript programmer might get confused here.

Erlang:

 "12" + 1   => (exception)
 "12" - 1    => (exception)
 "12" > 2    => true
 "12" > 15  => true

Javascript:

"12" + 1 => "121"
"12" - 1  => 11
"12" > 2 => true
"12" > 15 => false

Overloading plus to mean either plus or string concatenation is
horrible - and shouldn't "-" mean string subtraction (sometimes)

so "123" - 23 =>100 and "abc" - "a" => "bc" and "abc" - "c" => "ab"
:-)

I prefer the Erlang method - but then I'm probably somewhat biased here...

This (Javascript) horrible way of doing things caused me
hours of debugging - since my debugger wrote strings without
the quotes so I couldn't see the difference between "123" and 123
when printed.

Javascript also crashes late, and variables in closures 
can be changed after binding them into the closures ... and the
concurrency model is ... (isn't) - but it's fun to muck around with
and do things in the browser.


> Suppose I want to make a Key-Value database, where the Key can be anything,
> including 250 and "250" to make any form of ordered tree we need a total
> order over all the keys.

Indeed.  If we can have only one built-in comparison, it needs to be
total over all values.  It is a pity that Prolog's distinction between
term comparison and number comparison wasn't carried over to Erlang :-(


   Jeff Schultz
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Advanced Erlang Subtleties

James Churchman
a final one..

if you have for example a record like : 

-record(server_opts,{port, ip="127.0.0.1", max_connections=10}).

then you can obviously do :

make_a_record()->#server_opts{max_connections=100}.

and to update a record : 

update_a_record()->
MyRec = make_a_record(),
MyUpdatedRec = MyRec#server_opts{ip="10.20.30.40"}.

but you can't skip out the intermediate variable like so : 

update_a_record()->
make_a_record()#server_opts{ip="10.20.30.40"}.

it gives ":17: syntax error before: '#'"

but wrapping in round brackets allows this : 

update_a_record()->
(make_a_record())#server_opts{ip="10.20.30.40"}.

not sure why this is they case, but anyhow this might save a few lines :-)


On 20 May 2011 18:02, James Churchman <[hidden email]> wrote:
yes this is where i think erlang really gets the balance correct, being dynamically typed ( so far less repetitive defining types where the type is either obvious or does not matter and is just begin "messaged" threw .. tho i know other people are strongly in favour of static typing)  but not Loosely Typed so avoids the huge amounts of ambiguity like what happens when you "add" or "subtract" different types as does JS, leading to the bug showing up a huge distance way from the code that was at fault.. unfortunately JS also has hundreds of faults & bugs ( around 270 at my count ;-) ) in the original implementation that due to a big of a chicken / egg scenario cant really ever be changed :-) things like "crashing late" come from the fact that early versions had no error handling at all! ( eg no try catch )

>A nice example of how semantics is so much more important than syntax…

yes and shows that purely putting new syntax's onto an existing language only aid developer productivity & readability a bit and generally don't make the code (much) more reliable

On Fri, May 20, 2011 at 5:45 AM, Jeff Schultz <[hidden email]> wrote:
On Thu, May 19, 2011 at 11:02:35PM +0200, Joe Armstrong wrote:
> > I can see the equivalent evaluation of "250"<250 being a common mistake,
> > and always being false is hardly useful.

> A "<" B when A and B have different types means A is
> "less complex" than B. Lists and more complex than integers
> "250" is a list so "250"<250 is false.

Another thought I had about this was that in some languages
"10" < 20 might be true, and "20" < 10 or 20 < "10" might be false.

In Erlang a string (list) is always bigger (ie more complex) than an integer.

A Javascript programmer might get confused here.

Erlang:

 "12" + 1   => (exception)
 "12" - 1    => (exception)
 "12" > 2    => true
 "12" > 15  => true

Javascript:

"12" + 1 => "121"
"12" - 1  => 11
"12" > 2 => true
"12" > 15 => false

Overloading plus to mean either plus or string concatenation is
horrible - and shouldn't "-" mean string subtraction (sometimes)

so "123" - 23 =>100 and "abc" - "a" => "bc" and "abc" - "c" => "ab"
:-)

I prefer the Erlang method - but then I'm probably somewhat biased here...

This (Javascript) horrible way of doing things caused me
hours of debugging - since my debugger wrote strings without
the quotes so I couldn't see the difference between "123" and 123
when printed.

Javascript also crashes late, and variables in closures 
can be changed after binding them into the closures ... and the
concurrency model is ... (isn't) - but it's fun to muck around with
and do things in the browser.


> Suppose I want to make a Key-Value database, where the Key can be anything,
> including 250 and "250" to make any form of ordered tree we need a total
> order over all the keys.

Indeed.  If we can have only one built-in comparison, it needs to be
total over all values.  It is a pity that Prolog's distinction between
term comparison and number comparison wasn't carried over to Erlang :-(


   Jeff Schultz
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions



_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Loading...