Example code: map.erl, filter.erl
Anonymous Functions
Many languages, including Clojure, Scala, and Ruby, have the ability to define “anonymous” blocks of code. An important use of such code blocks is to transform a series of values from a collection (e.g., passing an anonymous function to the Clojure map function.)
Erlang supports anonymous functions. Because Erlang is dynamically-typed, an anonymous function can be assigned to a variable (Clojure allows this as well). You can think of an anonymous function as being a value, in much the same way that numbers, lists, and tuples are values in Erlang. Like all other values, they can be passed to functions and returned from functions.
Example:
1> Add1 = fun(N) -> N+1 end. #Fun<erl_eval.6.80247286> 2> Add1(3). 4
Here, we defined a function of one parameter N that adds 1 to its parameter and returns the result.
More interesting example:
3> AddN = fun(N) -> fun(X) -> N+X end end. #Fun<erl_eval.6.80247286> 4> Add12 = AddN(12). #Fun<erl_eval.6.80247286> 5> Add12(11). 23
In this example, the AddN function takes a parameter N and returns a function that adds N to its parameter X. By calling AddN with the argument value 12, we create a function that adds 12 to its parameter.
The concept of returning a function from a function is called currying.
Transforming Lists
Anonymous functions can be applied to lists to select or transform the values in the list.
One way to transform a list is to map a function onto each element of the list, producing a list of transformed values as a result.
Here is a possible implementation of a map function:
The implementation is quite simple. The base case is an empty list, where the result is simply the empty list. In the recursive case, the function F is applied to the first element of the list, and prepended onto the list that results from recursively applying F to the rest of the list.
Testing it on a list:
8> map:map(fun(N) -> N*2 end, [1, 2, 3]). [2,4,6]
Note that the implementation of map above is not tail-recursive. Here is a tail-recursive version:
Because the accumulator builds the result list with the transformed elements in reverse order, we apply the built-in lists:reverse function before returning the final result.
Another useful list-transformation technique is filtering a list to retain or discard elements matching a specified predicate:
Examples of using these functions:
18> filter:retain(fun(N) -> N > 4 end, [1, 2, 3, 4, 5, 6, 7, 8]). [5,6,7,8] 19> filter:discard(fun(N) -> N > 4 end, [1, 2, 3, 4, 5, 6, 7, 8]). [1,2,3,4]
Built-in versions
It’s interesting to build our own list-transformation functions, but in practice it’s better to use the built-in implementations, which are list::map, lists:takewhile, and lists:dropwhile.
List Comprehensions
List comprehensions are a concise syntax for describing transformations of one or more lists.
In the following examples, the variable List is defined as:
List = [1, 2, 3, 4, 5, 6, 7, 8].
Example: double each element in a list:
26> [N * 2 || N <- List]. [2,4,6,8,10,12,14,16]
Read this as “select elements N from List, and generate a new list by adding elements of the form N*2”.
Example: get all elements greater than 4:
27> [N || N <- List, N > 4]. [5,6,7,8]
Here, we’ve specified an additional clause N>4 to restrict which elements of List are used to generate the result list.
Example: double all elements greater than 4:
28> [N*2 || N <- List, N > 4]. [10,12,14,16]
In this example, we both selected and transformed the input list.