Category Archives: Erlang Thursday

Erlang Thursday – lists:zip/2

Today’s Erlang Thursday is lists:zip/2.

lists:zip/2 returns a new list of two-tuples, from the corresponding elements of the two lists passed as arguments.

lists:zip([a, b, c, d], [1, 2, 3, 4]).
% [{a,1},{b,2},{c,3},{d,4}]
lists:zip([a, b, c, d], [{1, 3}, {2, 5}, {3, 7}, {4, 11}]).
% [{a,{1,3}},{b,{2,5}},{c,{3,7}},{d,{4,11}}]
lists:zip([a, b], [fun lists:map/3, fun lists:foldl/3]).
% [{a,#Fun<lists.map.3>},{b,#Fun<lists.foldl.3>}]

If the lists are not of the same length a function clause match exception is thrown.

lists:zip([a, b, c, d], [1, 2, 3]).                        
% ** exception error: no function clause matching lists:zip([d],[]) (lists.erl, line 385)
%      in function  lists:zip/2 (lists.erl, line 385)
%      in call from lists:zip/2 (lists.erl, line 385)
lists:zip([a, b, c], [1, 2, 3, 4]).                        
% ** exception error: no function clause matching lists:zip([],[4]) (lists.erl, line 385)
%      in function  lists:zip/2 (lists.erl, line 385)
%      in call from lists:zip/2 (lists.erl, line 385)

There is also a 3-arity version of the zip function lists:zip3/3, which behaves like lists:zip/2 but takes three lists as arguments instead of two.

lists:zip3([a, b, c, d], [1, 2, 3, 4], ["alpha", "bravo", "charlie", "delta"]).         
% [{a,1,"alpha"},{b,2,"bravo"},{c,3,"charlie"},{d,4,"delta"}]

If you need to combine the arguments in a different manner, you can use lists:zipwith/3, or lists:zipwith3/4, each of which takes as the first argument a 2-arity combining function.

lists:zipwith(fun(X, Y) -> X * Y end, [1, 2, 3, 4], [2, 3, 4, 5]).
% [2,6,12,20]
lists:zipwith(fun(X, Y) -> X + Y end, [1, 2, 3, 4], [2, 3, 4, 5]).                                                
% [3,5,7,9]
lists:zipwith3(fun(X, Y, Z) -> X * Y * Z end, [1, 2, 3, 4], [2, 3, 4, 5], [4, 3, 2, 1]).
% [8,18,24,20]
lists:zipwith3(fun(X, Y, Z) -> {{X, Y}, Z} end, [a, b, c, d], [1, 2, 3, 4], ["alpha", "bravo", "charlie", "delta"]).
% [{{a,1},"alpha"},
%  {{b,2},"bravo"},
%  {{c,3},"charlie"},
%  {{d,4},"delta"}]

–Proctor

Erlang Thursday – lists:foldl/3 and lists:foldr/3

Today’s Erlang Thursday is lists:foldl/3 and lists:foldr/3.

lists:foldl/3 is Erlang’s version of the reduce function. lists:foldl/3 takes a function, an initial accumulator value, and a list, and returns a single value.

The first argument given to foldl is a function that takes two arguments, the item currently iterating over, and the accumulated value. The result of applying this function is used as new new value for the accumulator for the following item, or if no items are left it becomes the result of foldl.

The next argument to lists:foldl/3 is an initial value of the accumulator. This is different than some other languages, as those languages will take the initial value for the accumulator as an optional value and use the first item to be traversed as the default value for the accumulator. But in Erlang an initial value for the accumulator is a required argument to both lists:foldl/3 and lists:foldr/3.

The third, and last, argument to foldl is the list to iterate over.

lists:foldl(fun(X, Sum) -> Sum + X end, 0, [1, 2, 3, 4, 5]).
% 15
lists:foldl(fun(X, Product) -> Product * X end, 1, [1, 2, 3, 4, 5]).
% 120
lists:foldl(fun(X, Accum) -> io:format("~p ", [X]) end, void, [1, 2, 3, 4, 5]).
% 1 2 3 4 5 ok
lists:foldl(fun(X, Accum) -> io:format("~p ", [X]), Accum end, void, [1, 2, 3, 4, 5]).
% 1 2 3 4 5 void
lists:foldl(fun(X, Accum) -> Accum + X end, 1, []).               
% 1
lists:foldl(fun(X, Result) -> lists:umerge(Result, X) end, [], [[1, 2, 3], [3, 5, 8], [11, 13, 17]]).
% [1,2,3,5,8,11,13,17]

The Erlang lists module also contains the function foldr/3 which traverses the list from left to right, or from the last item in the list to the first.

lists:foldr(fun(X, Accum) -> io:format("~p ", [X]), Accum end, void, [1, 2, 3, 4, 5]).
% 5 4 3 2 1 void
lists:foldr(fun(X, Accum) -> Accum + X end, 1, []).
% 1

The Erlang documentation points out that lists:foldl/3 is generally preferable over lists:foldr/3 because lists:foldl/3 is tail recursive, where lists:foldr/3 is not.

Erlang Thursday – lists:map/2

Today’s Erlang Thursday features lists:map/2.

lists:map/2 takes two arguments, a “mapping” function which takes a single argument, and a list of Erlang terms. The result is a new list of items that is the result of mapping the function applied to each value in the original list.

lists:map(fun(X) -> X + 1 end, [1, 2, 3, 4]).
% [2,3,4,5]
lists:map(fun(X) -> X * X end, [1, 2, 3, 4]).
% [1,4,9,16]

Because strings in Erlang are just lists of integers, you can map against strings as well.

lists:map(fun(X) -> X - 1 end, "IBM").
% "HAL"

On Functions in Erlang

If you look at the first example above, you see that the first argument we are passing is fun(X) -> X + 1 end. This is Erlang’s syntax for an anonymous function. An anonymous function takes the form:

fun(Args1) OptionalGuardClause1 ->
        Expression1, Expression2;
   (Args2) OptionalGuardClause2 ->
        Expression3, Expression4;
   (Args3) OptionalGuardClause3 ->
        Expression5, Expression6;
end

Because we have the power of normal functions, except for being able to recursively call an anonymous function before version 17.0, we can use anonymous functions with multiple clauses when passing to map.

lists:map(fun(X) when is_atom(X) -> atom; (X) -> nil end, [1, x, {x}, [], 'B']).
[nil,atom,nil,nil,atom]

Passing Existing Named Functions to lists:map/1

While in some cases an inline anonymous function may work, many times we would want to have a named function for clarity. We can pass named functions to map by using the qualified name of the function – module:function_name/arity – prefaced with fun. The examples below use math:log10/1 and erlang:is_atom/1 to demonstrate.

lists:map(fun math:log10/1, [1, 10, 100, 1000, 10000]).
[0.0,1.0,2.0,3.0,4.0]
lists:map(fun erlang:is_atom/1, [1, x, {x}, [], 'B']).
[false,true,false,false,true]

–Proctor

Erlang Thursday – lists:flatten/1

Today’s Erlang Thursday function is lists:flatten/1.

lists:flatten/1 flattens out an arbitrarily deep list of Erlang terms, into a “flattened” list.

lists:flatten([]).
% []
lists:flatten([a, b, c]).
% [a,b,c]
lists:flatten([a, b, [1, [x, y], 3], c]).
% [a,b,1,x,y,3,c]
lists:flatten([a, b, [1, [x, {some, tuple}], 3], c]).    
% [a,b,1,x,{some,tuple},3,c]

Be warned though, it flattens out all lists, as seen here

lists:flatten([a, "foo", b]).    
% [a,102,111,111,b]

You get the above lists with numbers in it, because under the covers, a string is just a list of integers, so you get the ASCII character codes for the letters f and o in "foo".

If you want the string to “remain”, you need to use the string as a binary type like this:

lists:flatten([a, <<"foo">>, b]).                       
% [a,<<"foo">>,b]

And as a bonus, there is also a lists:flatten/2, that takes a list to flatten, and another argument tail, which is the value to append to the newly flattened list.

lists:flatten([a, [1, [b, [2]]]], [x, y, z]).
% [a,1,b,2,x,y,z]

–Proctor

Erlang Thursday – lists:max/1

Today’s Erlang function is lists:max/1.

lists:max/1 takes one argument, a list of at least one term, and returns the item that has the greatest value when compared to the other items in the list. The list can be composed of any Erlang term:

lists:max([1, 39, 7, 63, 27, 52, 16]).
% 63
lists:max([q, w, a, r, c, f, m, b]).      
% w
lists:max([[1, 2, 3], [1, 2, 4]]).
% [1,2,4]

Erlang has a distinct order between the different types of terms,

number < atom < reference < fun < port < pid < tuple < list < bit string

allowing for the list passed to lists:max/1 to be comprised of disparate types.

lists:max([1, 2, 3, 4, a]).         
% a
lists:max([1, a, [foo, bar], {baz}]).
% [foo,bar]

And because a string in Erlang is just a list of numbers under the covers, we can even compare strings.

lists:max(["foo", "bar", "baz", "snafu"]).
"snafu" 

If you pass an empty list to lists:max/1 a no function clause matching error will be raised, since it expects the list to be at least composed of one term.

lists:max([]).
** exception error: no function clause matching lists:max([]) (lists.erl, line 326)

What are some of your favorite Erlang functions, or even just ones you would like to see a future Erlang Thursday post about?

And don't forget to check out the last Tuesday's Ruby Tuesday on Enumerable#max, if you would like a comparison between Erlang's max function and Ruby's max method.

--Proctor

Erlang Thursday – lists:seq

My goal of these Erlang Thursday’s for the next while will be to show off the “corresponding” functions in Erlang for the Ruby methods I highlight on Tuesday. I am hoping that by having these Erlang Thursday line up with the Ruby Tuesday for the week, that I can make Erlang be less foreign and intimidating by establishing a common thread of discussion. I would love to know your thoughts on how well this secondary goal is working for you the reader.

That being said, since I talked about Ruby’s Range this week, today’s Erlang function is lists:seq/2 and lists:seq/3.

If you remember from the sidebar of last weeks Erlang Thursday, the number at the end of the function name means the arity (number of arguments) of the function.

The function lists:seq/2 takes a beginning and an ending integer and produces a list of integers including both the beginning and ending value.

lists:seq(1, 10).     
% [1,2,3,4,5,6,7,8,9,10]
lists:seq(1, 1). 
% [1]

The 3-arity version of lists:seq introduces the increment as the third argument:

lists:seq(1, 10, 3).  
% [1,4,7,10]

The increment value can be negative as well, allowing for counting down from one number to another.

lists:seq(20, 10, -1).
% [20,19,18,17,16,15,14,13,12,11,10]
lists:seq(20, 10, -5).
% [20,15,10]

The list:seq functions will throw an exception if given invalid starting and ending values, unlike the Ruby Range which returns an empty set.

lists:seq(10, 1).   
% ** exception error: no function clause matching lists:seq(10,1) (lists.erl, line 241)
lists:seq(1, 10, 0).
% ** exception error: no true branch found when evaluating an if expression
%      in function  lists:seq/3 (lists.erl, line 262)
lists:seq(1, 10, -2).
% ** exception error: no true branch found when evaluating an if expression
%      in function  lists:seq/3 (lists.erl, line 262)
lists:seq(10, 1, 2).
% ** exception error: no true branch found when evaluating an if expression
%      in function  lists:seq/3 (lists.erl, line 262)

–Proctor

Erlang Thursday – lists:member/2

To go with this weeks Ruby Tuesday post about Enumerable#include?, I decided I would highlight Erlang’s counterpart, lists:member/2.

Sidebar for those unfamiliar with Erlang: lists:member/2 is read as the function member with arity 2, which is found in the lists module. Arity being the number of arguments that the function takes. The module is important, because modules are the containers in which all functions must live — primarily because modules are the unit of code reloading in Erlang — but that way you know which specific member function is being referred to.

All that being said, the function member, in the lists module, takes two arguments: the item being looked for which is an Erlang term, and the list of Erlang terms to check; and returns true if the term is found in the list. An Erlang term is just a piece of data which can be of any of the Erlang data types.

For ability to try this out, the expression is normal, and the return value is the line that starts with the % symbol, which is the Erlang comment, to allow for copy and pasting into the Erlang shell erl.

It can be something as simple for checking if a number is in a list of numbers:

lists:member(4, [1, 2, 3, 5, 8, 13, 21]).
% false
lists:member(13, [1, 2, 3, 5, 8, 13, 21]).
% true

or if an atom is in a list of atoms:

lists:member(c, [a, b, c, d]).   
% true
lists:member(q, [a, b, c, d]).   
% false

or list of more complex terms such as a tuple, or tuples with lists inside them:

lists:member({d, 4}, [{a, 1}, {b, 2}, {c, 3}, {d, 4}]).
% true
lists:member({'Foo', [bar, baz]}, [a, 1, {'Foo', [bar, baz]}]). 
% true
lists:member({fu, [bar, baz]}, [a, 1, {'Foo', [bar, baz]}]).    
% false

or even an integer, or “character”, in a string:

lists:member($a, "banana").
% true
lists:member(97, "banana").
% true
lists:member($A, "banana").
% false

Which if you look carefully enough, you might be able to get the hint that a string in Erlang is really just a list of integers.

Hope this is an interesting comparison between Ruby and Erlang, and my give you some insight into Erlang if you are unfamiliar with the language.

–Proctor