0% found this document useful (0 votes)
3 views

CheatSheet Haskell

This Haskell cheat sheet provides a concise overview of fundamental elements including syntax, keywords, strings, enumerations, lists, tuples, and function definitions. It covers comments, reserved words, class and instance declarations, data types, and the use of the 'do' keyword for monadic contexts. Additionally, it highlights the importance of indentation and layout rules in Haskell programming.

Uploaded by

hejar80248
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

CheatSheet Haskell

This Haskell cheat sheet provides a concise overview of fundamental elements including syntax, keywords, strings, enumerations, lists, tuples, and function definitions. It covers comments, reserved words, class and instance declarations, data types, and the use of the 'do' keyword for monadic contexts. Additionally, it highlights the importance of indentation and layout rules in Haskell programming.

Uploaded by

hejar80248
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 13

Haskell Cheat Sheet Strings Enumerations

"abc" – Unicode string. [1..10] – List of numbers – 1, 2, . . ., 10.


This cheat sheet lays out the fundamental elements
'a' – Single character. [100..] – Infinite list of numbers – 100, 101,
of the Haskell language: syntax, keywords and
102, . . . .
other elements. It is presented as both an ex- Multi-line Strings Normally, it is syntax error if [110..100] – Empty list; ranges only go forwards.
ecutable Haskell file and a printable document. a string has any actual new line characters. That is, [0, -1 ..] – Negative integers.
Load the source into your favorite interpreter to this is a syntax error: [-100..-110] – Syntax error; need [-100.. -110]
play with code samples shown.
for negatives.
string1 = "My long [1,3..100], [-1,3..100] – List from 1 to 100 by
string." 2, -1 to 100 by 4.
Syntax
However, backslashes (‘\’) can be used to “escape”
In fact, any value which is in the Enum class can be
Below the most basic syntax for Haskell is given. around the new line:
used. E.g.,:
string1 = "My long \ ['a' .. 'z'] – List of characters – a, b, . . ., z.
Comments \string." [1.0, 1.5 .. 2] – [1.0,1.5,2.0].
[UppercaseLetter ..] – List of GeneralCategory
A single line comment starts with ‘--’ and extends The area between the backslashes is ignored. An values (from Data.Char).
to the end of the line. Multi-line comments start important note is that new lines in the string must
with ’{-’ and extend to ’-}’. Comments can be still be represented explicitly:
Lists & Tuples
nested.
Comments above function definitions should string2 = "My long \n\ [] – Empty list.
start with ‘{- |’ and those next to parameter types \string." [1,2,3] – List of three numbers.
with ‘-- ^’ for compatibility with Haddock, a sys- 1 : 2 : 3 : [] – Alternate way to write lists us-
tem for documenting Haskell code. That is, string1 evaluates to: ing “cons” (:) and “nil” ([]).
"abc" – List of three characters (strings are lists).
My long string. 'a' : 'b' : 'c' : [] – List of characters (same
Reserved Words as "abc").
While string2 evaluates to: (1,"a") – 2-element tuple of a number and a string.
The following lists the reserved words defined by (head, tail, 3, 'a') – 4-element tuple of two
Haskell. It is a syntax error to give a variable or My long functions, a number and a character.
function one of these names. string.

case, class, data, deriving, do, “Layout” rule, braces and semi-colons.
else, if, import, in, infix, infixl, Numbers
Haskell can be written using braces and semi-
infixr, instance, let, of, module, 1 - Integer colons, just like C. However, no one does. Instead,
newtype, then, type, where 1.0, 1e10 - Floating point the “layout” rule is used, where spaces represent

c 2008 Justin Bailey. 1 [email protected]


scope. The general rule is – always indent. When As can be seen above, the in keyword must also be case ch of
the compiler complains, indent more. in the same column as let. Finally, when multiple Nothing -> "No choice!"
Braces and semi-colons Semi-colons terminate
definitions are given, all identifiers must appear in Just (First _) -> "First!"
an expression, and braces represent scope. They
the same column. Just Second -> "Second!"
can be used after several keywords: where, let, do
_ -> "Something else."
and of. They cannot be used when defining a func- Keywords We can use argument capture to display the value
tion body. For example, the below will not compile. matched if we wish:
Haskell keywords are listed below, in alphabetical
square2 x = { x * x; } anyChoice2 ch =
order.
However, this will work fine: case ch of
Nothing -> "No choice!"
square2 x = result Case Just score@(First "gold") ->
where { result = x * x; } case is similar to a switch statement in C# or Java, "First with gold!"
but can take action based on any possible value for Just score@(First _) ->
Function Definition Indent the body at least the type of the value being inspected. Consider a "First with something else: "
one space from the function name: simple data type such as the following: ++ show score
_ -> "Not first."
square x =
data Choices = First String | Second |
x * x
Third | Fourth Matching Order Matching proceeds from top to
Unless a where clause is present. In that case, in- bottom. If we re-wrote anyChoice1 as below, we’ll
case can be used to determine which choice was
dent the where clause at least one space from the never know what choice was actually given because
given:
function name and any function bodies at least one the first pattern will always succeed:
space from the where keyword: whichChoice ch = anyChoice3 ch =
square x = case ch of case ch of
x2 First _ -> "1st!" _ -> "Something else."
where x2 = Second -> "2nd!" Nothing -> "No choice!"
x * x _ -> "Something else." Just (First _) -> "First!"
As with pattern-matching in function definitions,
Just Second -> "Second!"
Let Indent the body of the let at least one space
the ‘_’ character is a “wildcard” and matches any
from the first definition in the let. If let appears Guards Guards, or conditional matches, can be
value.
on its own line, the body of any definition must used in cases just like function definitions. The only
appear in the column after the let: Nesting & Capture Nested matching and argu- difference is the use of the -> instead of =. Here
ment capture are also allowed. Referring to the is a simple function which does a case-insensitive
square x = definition of Maybe below, we can determine if any string match:
let x2 = choice was given using a nested match:
x * x strcmp [] [] = True
in x2 anyChoice1 ch = strcmp s1 s2 = case (s1, s2) of

c 2008 Justin Bailey. 2 [email protected]


(s1:ss1, s2:ss2) the ‘|’ character. Note that type and constructor
| toUpper s1 == toUpper s2 -> instance Flavor Char where names must start with a capital letter. It is a syntax
strcmp ss1 ss2 flavor _ = "sour" error otherwise.
| otherwise -> False Constructors with Arguments The type above
Evaluating flavor True gives:
_ -> False is not very interesting except as an enumeration.
> flavor True Constructors that take arguments can be declared,
Class "sweet" allowing more information to be stored with your
While flavor 'x' gives: type:
A Haskell function is defined to work on a certain
type or set of types and cannot be defined more data Point = TwoD Int Int
> flavor 'x'
than once. Most languages support the idea of | ThreeD Int Int Int
"sour"
“overloading”, where a function can have different Notice that the arguments for each constructor are
behavior depending on the type of its arguments. Defaults type names, not constructors. That means this kind
Haskell accomplishes overloading through class Default implementations can be given for func- of declaration is illegal:
and instance declarations. A class defines one tions in a class. These are useful when certain func- data Poly = Triangle TwoD TwoD TwoD
or more functions that can be applied to any types tions can be defined in terms of others in the class.
which are members (i.e., instances) of that class. A A default is defined by giving a body to one of the instead, the Point type must be used:
class is analogous to an interface in Java or C, and member functions. The canonical example is Eq, data Poly = Triangle Point Point Point
instances to a concrete implementation of the inter- which can defined /= (not equal) in terms of ==. :
face. Type and Constructor Names Type and con-
A class must be declared with one or more type class Eq a where structor names can be the same, because they will
variables. Technically, Haskell 98 only allows one (==) :: a -> a -> Bool never be used in a place that would cause confu-
type variable, but most implementations of Haskell (/=) :: a -> a -> Bool sion. For example:
support so-called multi-parameter type classes, which (/=) a b = not (a == b)
data User = User String | Admin String
allow more than one type variable. In fact, recursive definitions can be created, but one which declares a type named User with two con-
We can define a class which supplies a flavor for class member must always be implemented by any structors, User and Admin. Using this type in a
a given type: instance declarations. function makes the difference clear:
class Flavor a where whatUser (User _) = "normal user."
flavor :: a -> String Data
whatUser (Admin _) = "admin user."
Notice that the declaration only gives the type sig- So-called algebraic data types can be declared as fol- Some literature refers to this practice as type pun-
nature of the function - no implementation is given lows: ning.
here (with some exceptions, see “Defaults” below). data MyType = MyValue1 | MyValue2 Type Variables Declaring so-called polymorphic
Continuing, we can define several instances: data types is as easy as adding type variables in the
MyType is the type’s name. MyValue1 and
instance Flavor Bool where MyValue are values of the type and are called con- declaration:
flavor _ = "sweet" structors. Multiple constructors are separated with data Slot1 a = Slot1 a | Empty1

c 2008 Justin Bailey. 3 [email protected]


This declares a type Slot1 with two constructors, If whichCon is called with a Noncon value, a runtime data Alarm = Soft | Loud | Deafening
Slot1 and Empty1. The Slot1 constructor can take error will occur. deriving (Read, Show)
an argument of any type, which is represented by Finally, as explained elsewhere, these names
the type variable a above. can be used for pattern matching, argument cap- It is a syntax error to specify deriving for any other
We can also mix type variables and specific ture and “updating.” classes besides the six given above.
types in constructors: Class Constraints Data types can be declared
with class constraints on the type variables, but Deriving
data Slot2 a = Slot2 a Int | Empty2
this practice is generally discouraged. It is gener- See the section on deriving under the data key-
Above, the Slot2 constructor can take a value of ally better to hide the “raw” data constructors us- word above.
any type and an Int value. ing the module system and instead export “smart”
constructors which apply appropriate constraints.
Record Syntax Constructor arguments can be Do
In any case, the syntax used is:
declared either positionally, as above, or using
record syntax, which gives a name to each argu- data (Num a) => SomeNumber a = Two a a The do keyword indicates that the code to follow
ment. For example, here we declare a Contact type | Three a a a will be in a monadic context. Statements are sepa-
with names for appropriate arguments: rated by newlines, assignment is indicated by <-,
This declares a type SomeNumber which has one and a let form is introduce which does not require
data Contact = Contact { ctName :: String type variable argument. Valid types are those in the in keyword.
, ctEmail :: String the Num class.
If and IO if is tricky when used with IO.
, ctPhone :: String } Deriving Many types have common operations Conceptually it is are no different, but intuitively
which are tedious to define yet very necessary, such it is hard to deal with. Consider the function
These names are referred to as selector or accessor
as the ability to convert to and from strings, com- doesFileExists from System.Directory:
functions and are just that, functions. They must
pare for equality, or order in a sequence. These
start with a lowercase letter or underscore and can-
not have the same name as another function in
capabilities are defined as typeclasses in Haskell. doesFileExist :: FilePath -> IO Bool
Because seven of these operations are so com-
scope. Thus the “ct” prefix on each above. Mul-
mon, Haskell provides the deriving keyword The if statement has this “signature”:
tiple constructors (of the same type) can use the
which will automatically implement the typeclass
same accessor function for values of the same type,
on the associated type. The seven supported type- if-then-else :: Bool -> a -> a -> a
but that can be dangerous if the accessor is not used
classes are: Eq, Read, Show, Ord, Enum, Ix, and
by all constructors. Consider this rather contrived That is, it takes a Bool value and evaluates to some
Bounded. other value based on the condition. From the type
example:
Two forms of deriving are possible. The first is
signatures it is clear that doesFileExist cannot be
data Con = Con { conValue :: String } used when a type only derives on class:
used directly by if:
| Uncon { conValue :: String }
data Priority = Low | Medium | High
| Noncon wrong fileName =
deriving Show
if doesFileExist fileName
whichCon con = "convalue is " ++ The second is used when multiple classes are de- then ...
conValue con rived: else ...

c 2008 Justin Bailey. 4 [email protected]


That is, doesFileExist results in an IO Bool value, else Export
while if wants a Bool value. Instead, the correct -- multiple statements require
value must be “extracted” by running the IO ac- -- a new 'do'. See the section on module below.
tion: do
f <- readFile args
right1 fileName = do If, Then, Else
putStrLn ("The file is " ++
exists <- doesFileExist fileName
show (length f) Remember, if always “returns” a value. It is an
if exists
++ " bytes long.") expression, not just a control flow statement. This
then return 1
else return 0 function tests if the string given starts with a lower
And one with case:
case letter and, if so, converts it to upper case:
Notice the use of return, too. Because do puts us countBytes2 =
“inside” the IO monad, we can’t “get out” except do -- Use pattern-matching to
through return. Note that we don’t have to use putStrLn "Enter a filename." -- get first character
if inline here - we can also use let to evaluate the args <- getLine sentenceCase (s:rest) =
condition and get a value first: case args of if isLower s
right2 fileName = do [] -> putStrLn "No args given." then toUpper s : rest
exists <- doesFileExist fileName file -> do else s : rest
let result = f <- readFile file -- Anything else is empty string
if exists putStrLn ("The file is " ++ sentenceCase _ = []
then 1 show (length f)
else 0 ++ " bytes long.")
Import
return result
An alternative is to provide semi-colons and braces.
See the section on module below.
Again, notice where return is. We don’t put it in A do is still required, but no indenting is needed.
the let statement. Instead we use it once at the end The below shows a case example but it applies to
of the function. if as well: In
Multiple do’s When using do with if or case, countBytes3 = See let.
another do is required if either branch has multiple do
statements. An example with if: putStrLn "Enter a filename."
countBytes1 f = args <- getLine Infix, infixl and infixr
do case args of
putStrLn "Enter a filename." [] -> putStrLn "No args given." See the section on operators below.
args <- getLine file -> do { f <- readFile file;
if length args == 0 putStrLn ("The file is " ++
Instance
-- no 'do'. show (length f)
then putStrLn "No filename given." ++ " bytes long."); } See the section on class above.

c 2008 Justin Bailey. 5 [email protected]


Let in "Initial three characters are: " ++ Imports The Haskell standard libraries are di-
show a ++ ", " ++ vided into a number of modules. The functionality
Local functions can be defined within a function us-
show b ++ ", and " ++ provided by those libraries is accessed by import-
ing let. let is always followed by in. in must ap-
show c ing into your source file. To import all everything
pear in the same column as the let keyword. Func-
exported by a library, just use the module name:
tions defined have access to all other functions and Note that this is different than the following, which
variables within the same scope (including those only works if the string has three characters: import Text.Read
defined by let). In this example, mult multiplies onlyThree str = Everything means everything: functions, data types
its argument n by x, which was passed to the orig- let (a:b:c) = str and constructors, class declarations, and even other
inal multiples. mult is used by map to give the in "The characters given are: " ++ modules imported and then exported by the that
multiples of x up to 10: show a ++ ", " ++ show b ++ module. Importing selectively is accomplished by
multiples x = ", and " ++ show c giving a list of names to import. For example, here
let mult n = n * x we import some functions from Text.Read:
in map mult [1..10] Of import Text.Read (readParen, lex)
let “functions” with no arguments are actually See the section on case above. Data types can imported in a number of ways. We
constants and, once evaluated, will not evaluate can just import the type and no constructors:
again. This is useful for capturing common por- Module
import Text.Read (Lexeme)
tions of your function and re-using them. Here is a
A module is a compilation unit which exports func-
silly example which gives the sum of a list of num- Of course, this prevents our module from pattern-
tions, types, classes, instances, and other modules.
bers, their average, and their median: matching on the values of type Lexeme. We can
A module can only be defined in one file, though
import one or more constructors explicitly:
listStats m = its exports may come from multiple sources. To
let numbers = [1,3 .. m] make a Haskell file a module, just add a module import Text.Read (Lexeme(Ident, Symbol))
total = sum numbers declaration at the top:
All constructors for a given type can also be im-
mid = head (take (m `div` 2) module MyModule where ported:
numbers)
in "total: " ++ show total ++ Module names must start with a capital letter but import Text.Read (Lexeme(..))
", mid: " ++ show mid otherwise can include periods, numbers and un-
We can also import types and classes defined in the
derscores. Periods are used to give sense of struc-
module:
Deconstruction The left-hand side of a let def- ture, and Haskell compilers will use them as indi-
inition can also deconstruct its argument, in case cations of the directory a particular source file is, import Text.Read (Read, ReadS)
sub-components are going to be accessed. This def- but otherwise they have no meaning.
In the case of classes, we can import the functions
inition would extract the first three characters from The Haskell community has standardized a set
defined for the using syntax similar to importing
a string of top-level module names such as Data, System,
constructors for data types:
Network, etc. Be sure to consult them for an appro-
firstThree str = priate place for your own module if you plan on import Text.Read (Read(readsPrec
let (a:b:c:_) = str releasing it to the public. , readList))

c 2008 Justin Bailey. 6 [email protected]


Note that, unlike data types, all class functions are A second form does not create an alias. Instead, A module can even re-export itself, which can be
imported unless explicitly excluded. To only import the prefix becomes the module name. We can write useful when all local definitions and a given im-
the class, we use this syntax: a simple function to check if a string is all upper ported module are to be exported. Below we export
case: ourselves and Data.Set, but not Data.Char:
import Text.Read (Read())
import qualified Char module AnotherBigModule (module Data.Set
, module AnotherBigModule)
Exclusions If most, but not all, names are going allUpper str =
where
to imported from a module, it would be tedious to all Char.isUpper str
specify all those names except a few. For that rea-
Except for the prefix specified, qualified imports import Data.Set
son, imports can also be specified via the hiding
support the same syntax as normal imports. The import Data.Char
keyword:
name imported can be limited in the same ways as
import Data.Char hiding (isControl described above. Newtype
, isMark) Exports If an export list is not provided, then all While data introduces new values and type just
functions, types, constructors, etc. will be available creates synonyms, newtype falls somewhere be-
Except for instance declarations, any type, function, to anyone importing the module. Note that any im- tween. The syntax for newtype is quite restricted –
constructor or class can be hidden. ported modules are not exported in this case. Limit- only one constructor can be defined, and that con-
Instance Declarations It must be noted that ing the names exported is accomplished by adding structor can only take one argument. Continuing
instance declarations cannot be excluded from im- a parenthesized list of names before the where key- the example above, we can define a Phone type like
port. Any instance declarations in a module will word: the following:
be imported when the module is imported. module MyModule (MyType
newtype Home = H String
, MyClass
Qualified Imports The names exported by a newtype Work = W String
, myFunc1
module (i.e., functions, types, operators, etc.) can data Phone = Phone Home Work
...)
have a prefix attached through qualified imports.
where As opposed to type, the H and W “values” on
This is particularly useful for modules which have
a large number of functions having the same name The same syntax as used for importing can be used Phone are not just String values. The typechecker
as Prelude functions. Data.Set is a good example: here to specify which functions, types, construc- treats them as entirely new types. That means our
tors, and classes are exported, with a few differ- lowerName function from above would not compile.
import qualified Data.Set as Set ences. If a module imports another module, it can The following produces a type error:
also export that module: lPhone (Phone hm wk) =
This form requires any function, type, constructor
or other name exported by Data.Set to now be pre- module MyBigModule (module Data.Set Phone (lower hm) (lower wk)
fixed with the alias (i.e., Set) given. Here is one way , module Data.Char)
Instead, we must use pattern-matching to get to the
to remove all duplicates from a list: where
“values” to which we apply lower:

removeDups a = import Data.Set lPhone (Phone (H hm) (W wk)) =


Set.toList (Set.fromList a) import Data.Char Phone (H (lower hm)) (W (lower wk))

c 2008 Justin Bailey. 7 [email protected]


The key observation is that this keyword does not Because type introduces a synonym, type checking strlen [] = result
introduce a new value; instead it introduces a new is not affected in any way. The function lower, de- where result = "No string given!"
type. This gives us two very useful properties: fined as: strlen f = result ++ " characters long!"
where result = show (length f)
• No runtime cost is associated with the new lower s = map toLower s
type, since it does not actually produce new Where vs. Let A where clause can only be de-
which has the type
values. In other words, newtypes are abso- fined at the level of a function definition. Usually,
lutely free! lower :: String -> String that is identical to the scope of let definition. The
• The type-checker is able to enforce that com- only difference is when guards are being used. The
can be used on values with the type FirstName or
mon types such as Int or String are used in scope of the where clause extends over all guards.
LastName just as easily: In contrast, the scope of a let expression is only
restricted ways, specified by the programmer.
lName (Person f l ) = the current function clause and guard, if any.
Finally, it should be noted that any deriving Person (lower f) (lower l)
clause which can be attached to a data declaration
can also be used when declaring a newtype. Because type is just a synonym, it can’t declare Declarations, Etc.
multiple constructors like data can. Type variables
The following section details rules on function dec-
can be used, but there cannot be more than the
Return larations, list comprehensions, and other areas of
type variables declared with the original type. That
the language.
See do above. means a synonym like the following is possible:

type NotSure a = Maybe a Function Definition


Type
but this not: Functions are defined by declaring their name, any
This keyword defines a type synonym (i.e., alias).
arguments, and an equals sign:
This keyword does not define a new type, like data type NotSure a b = Maybe a
or newtype. It is useful for documenting code but square x = x * x
otherwise has no effect on the actual type of a given Note that fewer type variables can be used, which
function or value. For example, a Person data type useful in certain instances. All functions names must start with a lowercase let-
could be defined as: ter or “_”. It is a syntax error otherwise.
Where Pattern Matching Multiple “clauses” of a func-
data Person = Person String String
tion can be defined by “pattern-matching” on the
Similar to let, where defines local functions and
where the first constructor argument represents values of arguments. Here, the the agree function
constants. The scope of a where definition is the
their first name and the second their last. How- has four separate cases:
current function. If a function is broken into multi-
ever, the order and meaning of the two arguments
ple definitions through pattern-matching, then the -- Matches when the string "y" is given.
is not very clear. A type declaration can help:
scope of a particular where clause only applies to agree1 "y" = "Great!"
type FirstName = String that definition. For example, the function result -- Matches when the string "n" is given.
type LastName = String below has a different meaning depending on the agree1 "n" = "Too bad."
data Person = Person FirstName LastName arguments given to the function strlen: -- Matches when string beginning

c 2008 Justin Bailey. 8 [email protected]


-- with 'y' given. isEven 0 = True Matching & Guard Order Pattern-matching
agree1 ('y':_) = "YAHOO!" isEven 1 = False proceeds in top to bottom order. Similarly, guard
-- Matches for any other value given. isEven (n + 2) = isEven n expressions are tested from top to bottom. For ex-
agree1 _ = "SO SAD." ample, neither of these functions would be very in-
Note that the ‘_’ character is a wildcard and Argument Capture Argument capture is useful teresting:
matches any value. for pattern-matching a value AND using it, with-
allEmpty _ = False
Pattern matching can extend to nested values. out declaring an extra variable. Use an @ symbol
in between the pattern to match and the variable to allEmpty [] = True
Assuming this data declaration:
assign the value to. This facility is used below to
data Bar = Bil (Maybe Int) | Baz capture the head of the list in l for display, while alwaysEven n
also capturing the entire list in ls in order to com- | otherwise = False
and recalling Maybe is defined as:
pute its length: | n `div` 2 == 0 = True
data Maybe a = Just a | Nothing
we can match on nested Maybe values when Bil is len ls@(l:_) = "List starts with " ++ Record Syntax Normally pattern matching oc-
present: show l ++ " and is " ++ curs based on the position of arguments in the
show (length ls) ++ " items long." value being matched. Types declared with record
f (Bil (Just _)) = ... len [] = "List is empty!" syntax, however, can match based on those record
f (Bil Nothing) = ...
names. Given this data type:
f Baz = ...
Guards Boolean functions can be used as
Pattern-matching also allows values to be assigned “guards” in function definitions along with pattern data Color = C { red
to variables. For example, this function determines matching. An example without pattern matching: , green
if the string given is empty or not. If not, the value , blue :: Int }
captures in str is converted to to lower case: which n
| n == 0 = "zero!" we can match on green only:
toLowerStr [] = [] | even n = "even!"
toLowerStr str = map toLower str | otherwise = "odd!" isGreenZero (C { green = 0 }) = True
In reality, str is the same as _ in that it will match isGreenZero _ = False
Notice otherwise – it always evaluates to true and
anything, except the value matched is also given a
can be used to specify a “default” branch. Argument capture is possible with this syntax,
name.
Guards can be used with patterns. Here is a though it gets clunky. Continuing the above, now
n + k Patterns This sometimes controversial function that determines if the first character in a define a Pixel type and a function to replace values
pattern-matching facility makes it easy to match string is upper or lower case: with non-zero green components with all black:
certain kinds of numeric expressions. The idea
is to define a base case (the “n” portion) with a what [] = "empty string!" data Pixel = P Color
constant number for matching, and then to define what (c:_)
other matches (the “k” portion) as additives to the | isUpper c = "upper case!" -- Color value untouched if green is 0
base case. Here is a rather inefficient way of testing | isLower c = "lower case" setGreen (P col@(C { green = 0 })) = P col
if a number is even or not: | otherwise = "not a letter!" setGreen _ = P (C 0 0 0)

c 2008 Justin Bailey. 9 [email protected]


Lazy Patterns This syntax, also known as ir- As long as the value x is not actually evaluated, ups =
refutable patterns, allows pattern matches which al-we’re safe. None of the base types need to look at x [c | c <- [minBound .. maxBound]
ways succeed. That means any clause using the (see the “_” matches they use), so things will work , isUpper c]
just fine.
pattern will succeed, but if it tries to actually use
Or to find all occurrences of a particular break
the matched value an error may occur. This is gen- One wrinkle with the above is that we must
value br in a list word (indexing from 0):
erally useful when an action should be taken on provide type annotations in the interpreter or the
code when using a Nothing constructor. Nothing
the type of a particular value, even if the value isn’t idxs word br =
present. has type Maybe a but, if not enough other informa- [i | (i, c) <- zip [0..] word
For example, define a class for default values: tion is available, Haskell must be told what a is. , c == br]
Some example default values:
class Def a where A unique feature of list comprehensions is that pat-
defValue :: a -> a -- Return "Just False" tern matching failures do not cause an error - they
defMB = defValue (Nothing :: Maybe Bool) are just excluded from the resulting list.
The idea is you give defValue a value of the right -- Return "Just ' '"
type and it gives you back a default value for that defMC = defValue (Nothing :: Maybe Char)
type. Defining instances for basic types is easy: Operators

instance Def Bool where List Comprehensions There are very few predefined “operators” in
defValue _ = False Haskell - most that do look predefined are actu-
A list comprehension consists of three types of el- ally syntax (e.g., “=”). Instead, operators are simply
ements - generators, guards, and targets. A list com- functions that take two arguments and have special
instance Def Char where
prehension creates a list of target values based on syntax support. Any so-called operator can be ap-
defValue _ = ' '
the generators and guards given. This comprehen- plied as a normal function using parentheses:
Maybe is a littler trickier, because we want to get sion generates all squares:
a default value for the type, but the constructor 3 + 4 == (+) 3 4
squares = [x * x | x <- [1..]]
might be Nothing. The following definition would To define a new operator, simply define it as a nor-
work, but it’s not optimal since we get Nothing x <- [1..] generates a list of all Integer values mal function, except the operator appears between
when Nothing is passed in. and puts them in x, one by one. x * x creates each
the two arguments. Here’s one which takes inserts
element of the list by multiplying x by itself.
instance Def a => Def (Maybe a) where a comma between two strings and ensures no extra
Guards allow certain elements to be excluded.
defValue (Just x) = Just (defValue x) spaces appear:
The following shows how divisors for a given num-
defValue Nothing = Nothing ber (excluding itself) can be calculated. Notice how first ## last =
We’d rather get a Just (default value) back instead.
d is used in both the guard and target expression. let trim s = dropWhile isSpace
Here is where a lazy pattern saves us – we can pre- divisors n = (reverse (dropWhile isSpace
tend that we’ve matched Just x and use that to get [d | d <- [1..(n `div` 2)] (reverse s)))
a default value, even if Nothing is given: , n `mod` d == 0] in trim last ++ ", " ++ trim first

instance Def a => Def (Maybe a) where Comprehensions are not limited to numbers. Any > " Haskell " ## " Curry "
defValue ~(Just x) = Just (defValue x) list will do. All upper case letters can be generated: Curry, Haskell

c 2008 Justin Bailey. 10 [email protected]


Of course, full pattern matching, guards, etc. are The results are surprising: isL33t _ = False
available in this form. Type signatures are a bit dif- toL33t 'o' = '0'
ferent, though. The operator “name” must appear > 2 + 3 * 5 toL33t 'a' = '4'
in parentheses: 17 -- etc.
> 2 `plus1` 3 `mult1` 5 toL33t c = c
(##) :: String -> String -> String 25
Notice that l33t has no arguments specified. Also,
Allowable symbols which can be used to define op- Reversing associativity also has interesting effects.
the final argument to convertOnly is not given.
erators are: Redefining division as right associative:
However, the type signature of l33t tells the whole
# $ % & * + . / < = > ? @ \ ^ | - ~ infixr 7 `div1` story:
div1 a b = a / b
However, there are several “operators” which can- l33t :: String -> String
not be redefined. Those are: We get interesting results:
<- -> = (by itself ) That is, l33t takes a string and produces a string.
> 20 / 2 / 2 It is a “constant”, in the sense that l33t always re-
Precedence & Associativity The precedence
5.0 turns a value that is a function which takes a string
and associativity, collectively called fixity, of any and produces a string. l33t returns a “curried”
> 20 `div1` 2 `div1` 2
operator can be set through the infix, infixr and form of convertOnly, where only two of its three
20.0
infixl keywords. These can be applied both to arguments have been supplied.
top-level functions and to local definitions. The This can be taken further. Say we want to write
syntax is: Currying
a function which only changes upper case letters.
In Haskell, functions do not have to get all of We know the test to apply, isUpper, but we don’t
infix | infixr | infixl precedence op
their arguments at once. For example, consider the want to specify the conversion. That function can
where precedence varies from 0 to 9. Op can actu-
convertOnly function, which only converts certain be written as:
elements of string depending on a test:
ally be any function which takes two arguments
convertUpper = convertOnly isUpper
(i.e., any binary operation). Whether the operator convertOnly test change str =
is left or right associative is specified by infixl or map (\c -> if test c which has the type signature:
infixr, respectively. infix declarations have no as- then change c
sociativity. convertUpper :: (Char -> Char)
else c) str
Precedence and associativity make many of the -> String -> String
rules of arithmetic work “as expected.” For ex- Using convertOnly, we can write the l33t function
which converts certain letters to numbers: That is, convertUpper can take two arguments. The
ample, consider these minor updates to the prece-
first is the conversion function which converts indi-
dence of addition and multiplication:
l33t = convertOnly isL33t toL33t vidual characters and the second is the string to be
infixl 8 `plus1` where converted.
plus1 a b = a + b isL33t 'o' = True A curried form of any function which takes
infixl 7 `mult1` isL33t 'a' = True multiple arguments can be created. One way to
mult1 a b = a * b -- etc. think of this is that each “arrow” in the function’s

c 2008 Justin Bailey. 11 [email protected]


signature represents a new function which can be The above is a bit verbose and we can rewrite us- Of course, lambdas can be the returned from func-
created by supplying one more argument. ing record syntax. This kind of “update” only sets tions too. This classic returns a function which will
values for the field(s) specified and copies the rest: then multiply its argument by the one originally
Sections Operators are functions, and they can
given:
be curried like any other. For example, a curried noGreen2 c = c { green = 0 }
version of “+” can be written as: multBy n = \m -> n * m
Above, we capture the Color value in c and return
add10 = (+) 10 a new Color value. That value happens to have the For example:
same value for red and blue as c and it’s green
However, this can be unwieldy and hard to read.
component is 0. We can combine this with pattern > let mult10 = multBy 10
“Sections” are curried operators, using parenthe-
matching to set the green and blue fields to equal > mult10 10
ses. Here is add10 using sections:
the red field: 100
add10 = (10 +)
makeGrey c@(C { red = r }) =
c { green = r, blue = r } Type Signatures
The supplied argument can be on the right or left,
which indicates what position it should take. This Haskell supports full type-inference, meaning in
Notice we must use argument capture (“c@”) to get
is important for operations such as concatenation: most cases no types have to be written down. Type
the Color value and pattern matching with record
signatures are still useful for at least two reasons.
onLeft str = (++ str) syntax (“C { red = r}”) to get the inner red field.
onRight str = (str ++)
Documentation – Even if the compiler can figure
Anonymous Functions out the types of your functions, other pro-
Which produces quite different results:
An anonymous function (i.e., a lambda expression grammers or even yourself might not be able
> onLeft "foo" "bar" or lambda for short), is a function without a name. to later. Writing the type signatures on all
"barfoo" They can be defined at any time like so: top-level functions is considered very good
> onRight "foo" "bar" form.
"foobar" \c -> (c, c)
Specialization – Typeclasses allow functions with
which defines a function which takes an argument overloading. For example, a function to
“Updating” values and record syntax
and returns a tuple containing that argument in negate any list of numbers has the signature:
Haskell is a pure language and, as such, has no both positions. They are useful for simple func-
mutable state. That is, once a value is set it never tions which don’t need a name. The following de- negateAll :: Num a => [a] -> [a]
changes. “Updating” is really a copy operation, termines if a string has mixed case (or is all whites-
with new values in the fields that “changed.” For pace): However, for efficiency or other reasons you
example, using the Color type defined earlier, we may only want to allow Int types. You would
can write a function that sets the green field to zero mixedCase str =
accomplish that with a type signature:
easily: all (\c -> isSpace c ||
isLower c ||
noGreen1 (C r _ b) = C r 0 b isUpper c) str negateAll :: [Int] -> [Int]

c 2008 Justin Bailey. 12 [email protected]


Type signatures can appear on top-level func- Unit
tions and nested let or where definitions. Gen- neg y | y > 0 = negate y
() – “unit” type and “unit” value. The value and
erally this is useful for documentation, though in | otherwise = y
type that represents no useful information.
some case you may use it prevent polymorphism.
A type signature is first the name of the item which
will be typed, followed by a ::, followed by the Type Annotations
Contributors
types. An example of this has already been seen Sometimes Haskell will not be able to deter-
above. mine what type you meant. The classic demonstra- My thanks to those who contributed patches and
Type signatures do not need to appear directly tion of this is the “show . read” problem: useful suggestions: Dave Bayer, Cale Gibbard,
above their implementation. They can be specified Stephen Hicks, Kurt Hutchinson, Adrian Neu-
anywhere in the containing module (yes, even be- canParseInt x = show (read x) mann, Markus Roberts, Holger Siegel, Leif Warner,
low!). Multiple items with the same signature can and Jeff Zaroyko.
Haskell cannot compile that function because it
also be defined together:
does not know the type of x. We must limit the
pos, neg :: Int -> Int type through an annotation: Version
... canParseInt x = show ((read x) :: Int) This is version 1.5. The source can be found
at GitHub1 . The latest released version of the
pos x | x < 0 = negate x Annotations have a similar syntax as type signa- PDF can be downloaded from Hackage2 . Visit
| otherwise = x tures, except they appear in-line with functions. CodeSlower.com3 for other projects and writings.

1 git://github.com/m4dc4p/cheatsheet.git
2 http://hackage.haskell.org/cgi-bin/hackage-scripts/package/CheatSheet
3 http://blog.codeslower.com

c 2008 Justin Bailey. 13 [email protected]

You might also like