The following is a pair of commands in Haskell that creates a list of numbers and performs a calculation on that list to generate a new list. This is called a list-comprehension.
list_1to100 = [ 1..100 ] list_2to200 = [ a*2 | a <- list_1to100 ]
The first statement can be read as
Define list_1to100 as a list from 1 to 100, but don’t actually generate a list from 1 to 100, yet. We just want to know it can be done.
This doesn’t physically create the list. It just lets the computer know it should be able to do this, if requested later on. It is easy to fail to grasp the implications of this, so re-read this a few times before reading on.
The computer waits until the invocation to evaluate the variable. This divorce between definition and execution is called Lazy Evaluation.
The second statement,
list_2to200 = [ a*2 | a <- list_1to100 ]
can be thought of in terms of looping constructs that are familiar to structured (ie. C-like) languages. It would go something like this:
Loop through list_1to100 and calculate the value
a*2 for each item, a, in the list and store it into list_2to200.
But this is not completely accurate. Since Haskell is lazily evaluated, it can be more accurately read as:
Define list_2to200 as the expression
a*2 and define a as a temporary variable that iterates through every element of list_1to100.
You then tell the computer to calculate these values by executing the name of the variable (“invoking” it), causing a chain reaction of calculations, whereas in C-like languages, invoking a variable simply coughs up a stored value from memory, which must be populated beforehand by a function. Thus, in lazily evaluated functional languages, the concepts of functions and data are interchangeable. This is the concept of first-class functions, and we say that “functions are first-class” in functional languages.
For a deeper exploration of functions in C-like languages, read my blog post: Functions in C-like Languages.