Function Factory
Books are made out of sentences strung together, melodies are made out of notes in sequence, and Haskell programs are made out of functions that call each other. In other languages, we spend considerable time modeling classes and putting together statements that produce side effects. In Haskell, every single computation we express is a chain of function calls. Given their importance, you should not be surprised to learn that Haskell provides at least four distinct ways to define functions.
We've already seen the first way of defining a function: through a named definition. For example, this Haskell code defines a function that produces a diminutive form of a regular Spanish noun or adjective:
diminutive :: String -> String
diminutive word = stem ++ "it" ++ gender
where
stem = init word -- discard last letter
gender = last word -- extract last letter, a or o
These calls illustrate its use:
diminutive "hermano" -- yields "hermanito", little brother
diminutive "abuela" -- yields "abuelita", little grandma
diminutive "mesa" -- yields "mesita", little table
The named definition syntax is the most verbose of the four. In this chapter, you will learn three shorter ways of defining functions. Additionally, you will explore several other features of functions that reduce the labor of writing Haskell programs. By the end of the chapter, you will be able to answer the following questions:
- How do lambdas, partial application, and composition—the remaining three ways of defining functions—ease the task of programming?
- How is pattern matching and destructuring used to simplify conditional logic and compound data structures?
- How are map, filter, fold, and other higher-order functions implemented and used?
- What should happen when a function has free variables but it outlives the scope in which the variables are bound? Should that even be legal?
Haskell is a function factory where functions are defined or called on nearly every keystroke. Because of its dogged commitment to functions, Haskell's developers have pioneered or popularized a number of features that lighten the burden of defining new functions. Newer languages like Rust have supported many of these features from the start, and older languages like Java have adapted to support them.