Haskell Book: Chapter 2 — Hello, Haskell!!
Expressions — Everything in Haskell is either an expression, or a declaration. All expressions evaluate to a result, and expressions can be composed of other expressions. Expressions are the most basic unit of a Haskell program.
Programs — In functional programming, programs themselves are simply an expression composed of many smaller expressions.
Declarations — Declarations are top-level bindings used to name expressions. Again, everything in Haskell is either an expression or a declaration.
Normal Form — Normal form occurs when an expression can no longer be reduced. Since a reduction occurs when a function is applied to its arguments, normal form occurs when we either run out of functions to apply, or we run out of arguments to apply to a function.
Functions — Functions allow us to abstract the parts of a programming pattern that don’t vary, and pass in the parts that do vary as arguments. In other words, functions allow us to abstract, in the general sense of that word. That’s why functions and abstractions are synonymous. Functions allow us to factor out the pattern that we want so that it can be used with different inputs.
Parameter vs. Argument — Although the terms parameter and argument are often used interchangeably. they are not the same thing. A parameter is used when declaring a function, and it is the name that will be bound to the argument(s) that the function is applied to.
Laziness — Haskell is lazy by default. This means that expressions are not evaluated until their results are needed. Even when an expression is evaluated, Haskell will only evaluate as much of the expression as needed. This allow us to have infinite structures in program, as long as we only use a finite amount of that structure.
Operators — Operators are functions, which are simply used with infix notation. We can convert between infix and prefix notation using backquotes and parenthesis. Partially applied operators are called sections. Note that the expression
(+1) is not the same as
Layout — In Haskell, whitespace is significant. Haskell uses a system called layout that determines how whitespace is interpreted. Usually, wherever we would see semicolons or curly braces, we instead us indentation. (In other words, we use indentation to create code blocks).
Negative Numbers — Negative numbers get special treatment in Haskell. This is because the token
— is overloaded. In certain contexts, it is an alias for
negate, while in other contexts, it is an alias for
negate is an infix, unary operator, which takes a number and returns a new number with the sign flipped.
subtract, on the other hand, is an infix operator that takes two numbers and subtracts the second from the first. Sometimes, Haskell will assume one usage over the other, and it can be a source of confusion when starting out.
The $ Operator — The
$ operator is an infix function with the lowest possible precedence of
0. The function simply applies its left-hand operand to its right-hand operand. It is used in idiomatic Haskell code in place of parentheses.
Let vs. Where — let and where are both used to create local bindings, but the way they work are very different. let is an expression. This means that it can be used wherever an expression is valid. where is a declaration that is part of a syntactic structure, and is only valid within that syntactic structure. let bindings are available later in the let block, as well as in the in block. where bindings are available later in the where block, in guards, and in the function body.
Terminology / Concepts
:: — Double colon is read as “has the type”.
Reducible Expression (redex) — an expression that is not yet in normal form — i.e., it can be further simplified.
Fixity — Haskell functions have the concept of associativity and precedence, which together are known as fixity.
Precedence — A number from 0–9, with a higher number meaning greater precedence. 10 is reserved for function application.
Associative — An associative operation means that, no matter how we parenthesize an operation, the result is the same. For example:
(1 + 2) + 3 = 1 + (2 + 3).
Commutative — A commutative operation is one in which, no matter how we order the operation, the result is the same. For example:
1 + 2 + 3 = 3 + 2 + 1. An easy mnemonic is to think of commute.
Integral — Integral means integer. When we are using integral math, that indicates we are operation on, and returning integers. For example,
div 1 3 reduces to
Syntactic Sugar — Syntactic sugar is a more concise, “nicer” way of writing a piece of code. Syntactic sugar does not change the semantics — i.e., the meaning — of a piece of code; it only changes the syntax.
Scope — scope refers to the area of code — usually a code block — within which a given binding applies.
The idea that programs in Haskell are simply an expression which can, in turn, be made up of smaller expressions is very powerful. In the words of Abelson & Sussman, this means that expressions are the method of composition in Haskell.
Another interesting subject in this chapter is the
$ operator. Again, it can be used in place of parentheses, but let’s think about how it works.
The important thing to note about this process is that we had to keep looking a the right-hand operand of the
$ operator because of that operator’s right-associativity.