Quick glance at Haskell for Experienced Developers
This blog is intended to get experienced devs a quick grasp of Haskell language in general, at least having knowledge enough to read 80%-90% of Haskell code.
Features
- Lazy evaluation by default
- Garbage Collection memory model.
- Type class!
- Force function pureness.
Help!
- Download & Install from Haskell Download page.
- Hoogle - Haskell's search engine for library.
- Hackage - Haskell's package document repository.
Command Line Tools
ghc
- Compilerghci
- REPLcabal
- Package managerrunhaskell
- Run an Haskell module
Project creation with Cabal
cabal init
to initialize a new project file. There are two kinds of project: Library or Executable.cabal configure
to configure a project (with compilation options?)cabal build
to build the current project.cabal run
to run the project.
Project Organisation
Comments
-- This is a line comment
{- This is a multi-line comment block.
...
end block with -}
Module
Module in Haskell is per source file (.hs
extension). A module file begins with following code:
module <Name> [(publicFunction1, publicFunction2, ...)] where
-- Module's code --
publicFunction1 :: Int -> Int
To import a module into another module, using import
keyword.
import <ModuleName> -- import all public functions into current namespace
import <ModuleName> (<function1>, <function2>, ...) -- import specific functions
import <ModuleName> hiding (<function1>, <function2>, ...) -- import all, exlude specific functions
import qualified <ModuleName> -- import all into a namespace <ModuleName>
import qualified <ModuleName> as <Namespace> -- import into a specific namespace <Namespace>
Name of the source file must be as same as module. If module name is Main
, the source file must also be Main.hs
.
Module name can contain sub-modules in the name, e.g. Program.Main
is also a valid module name. However, this module file Main.hs
has to be in a directory Program
.
Main entry point
Program entry point is defined by a value main
. For example, the default main entry point generated by Cabal looks like this.
module Main where
-- "main" value is an IO of unit.
main :: IO () -- declaration
main = putStrLn "Hello, Haskell!" -- body
Package (Library)
Haskell manages package via a tool called Cabal. A library project initialised from Cabal can be installed into another project by following command:
cd /to/myproject/
cabal install /path/to/library/
In order to install a package from a remote repository (default is Hackage), use cabal install <PackageName>
.
Cabal also supports package installation via archive file.
cabal install foo-1.0.tar.gz
cabal install http://example.com/foo-1.0.tar.gz
Install package to project repository
Use cabal sandbox init
in your project directory to create a project package repository. This is just like Node's node_modules/
, Python virtualenv
, or .NET nuget
.
After the sandbox creation, all cabal install
will put in the project repository instead of the global, machine-level repository.
In order to install to the global in a sandbox project, use runhaskell Setup install
instead of Cabal to install package into the global repository.
Prelude package
Prelude is a default package which is automatically imported. Haskell common functions and type definitions are contained here.
Language Specification
Data Types
Some type has dedicated package which is written in emphasized text.
Native
Bool
Prelude - e.g.True
,False
Char
Data.Char - e.g.'a'
,'b'
,'c'
,'\97'
(decimal),'\x07'
(hexdecimal)Int
(bounded) - e.g.123
,333
,555
Integer
(unbounded) - a.k.a Big Integer. e.g.123
,333
Float
- Single precision floating-point. e.g.123.45
Double
- Double precision floating-point e.g.123.45
[Char]
orString
- list of characters e.g."Hello"
[t]
Data.List - list of typet
e.g. list ofInt
: Either[1,2,3]
,[1..3]
, or1:2:3:[]
(concatenation form) is all the same.- List can be open-end for infinite members. e.g.
[1..]
- List in most functional languages is represented by singly-linked list.
- List can be open-end for infinite members. e.g.
(t1, t2, ..., tN)
- Tuple of N dimensions.- As a simple coordinate
(Int,Int)
. e.g.(4,9)
- As an anonymous record
([Char], [Char], Int)
. e.g.("Mr.", "John Dol", 1974)
- Tuple of 0 dimensions is
()
for both type and value (think ofvoid
/unit
type).
- As a simple coordinate
Other interesting types
Ratio
Data.Ratio - e.g.2 % 3
,4 % 5
Polymorphic (Type classes)
For Object-Oriented reader, just simply think of these types as interface. Following is same of common type classes:
Num
- any value represents integer.Fractional
- any value represents floating point.Eq
- support equality.Ord
- support ordering.Show
- support "to string" function.Read
- support string parsing.Enum
- support enumeration.Bounded
- support maximum value and minimum value.
List Comprehension
Instead of writing let x = [2,4,6,8,10]
, we can use list comprehension syntax like let x = [ 2*a | a <- [1..5]]
.
Other uses:
-- with a condition
let x = [a | a <- [1..5], a `mod` 2 == 0]
-- multiple sources
let y = [ x*y | x <- [1..5], y <- [1..5], x > y]
-- multiple conditions
let z = [ x | x <- [1..1000], x `div` 4 == 0, x `div` 5 == 0]
-- create repeated items
let i = ["item" | _ <- [1..10]]
_
(underscore) is a special symbol in Haskell. When being used in place of a symbolic name, it represents an ignored (unused) value.
Discriminated/Tagged Union
Define by using keyword data
and specify members separated by |
.
data Bool = True | False
data Domestic = Cat | Dog | Rat
-- assign value
mypet = Cat -- mypet's type is Domestic.
Member can hold data by declaring type of data afterward.
data Suit = Spade | Club | Diamond | Heart
data Face = Jack | Queen | King | Ace | Common Int
data Card = Card Suit Face
mycard = [Card Heart Queen, Card Diamond (Common 9)]
Record Type
Record is an extension of Tagged Union, instead of using an ordinary type, bracket form is used instead.
data Person = Person { firstName :: String
, lastName :: String
, age :: Int
, height :: Float
, phoneNumber :: String
, flavor :: String
}
Union with Type Parameter
data Maybe a = Nothing | Some a -- when a is a generic type
Type Alias
type String = [Char]
type Array a = [a] -- generic in type alias
Value declaration
Just simply declare a value with assignment operator =
x = [1,2,3] -- define x as list of 1,2,3
Function
Lambda Expression
\n -> n + 1
Named function
Function in Haskell starts with lowercase. Here a greeting function with one parameter:
greeting :: [Char] -> [Char]
greeting name = "Hello, " ++ name
The first line is a function declaration. It states the function name and its signature. In this example, function greeting
gets a string and return a string. The body of function must follow the declaration.
The declaration is optional. Haskell can infer parameter types and return type from a function body, but declaration can help understanding function better.
Note that all Haskell functions are curried function.
myadd x y = x + y
-- usage
let add100 = myadd 100
let result = add100 23 {- result = 123 -}
Generic Function
Generic type in function declaration uses lowercase name.
doubleListLength :: [a] -> Int
doubleListLength list = length (list ++ list)
In this example, a
is a generic type.
This doubleListLength
function can be used with either [1..10]
or "A string"
([Char]
type).
Generic Constraints
For example, the declaration of +
operator in Haskell:
(+) :: Num a => a -> a -> a
Anything before =>
is constraints, which are represented by Typeclasses. Here, (+)
function takes 2 values and return a value, all values must support Num
.
Function with pattern matching
Let me borrow an example from http://learnyouahaskell.com/syntax-in-functions
lucky :: (Integral a) => a -> String
lucky 7 = "LUCKY NUMBER SEVEN!"
lucky x = "Sorry, you're out of luck, pal!"
This function takes an integral parameter and returns a string. It matches the integer value with 7
and returns a string according to the matching.
Matching is not limited to value, but also data structure. For example, tuple and list can also be matched with following patterns:
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
first :: (a, b) -> a
first (x, _) = x
second :: (a, b) -> b
second (_, y) = y
_
(underscore) in pattern is a wildcard, it can match any value.
List has special syntax <element>:<tail>
which can separate the first element and the rest.
sum' :: (Num a) => [a] -> a
sum' [] = 0 -- match an empty list
sum' [x] = x -- match a single element list
sum' [x,y] = x + y -- match a list with 2 elements
sum' (x:xs) = x + sum' xs -- match any length of list.
Note that x
is a Num
value and xs
is a list of Num
.
capital :: String -> String
capital "" = "Empty string, whoops!"
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
all@(x:xs)
matches any list and takes the list value into symbol all
.
Case expression
Haskell has case expression which matches a specific value instead of pattern matching in function parameter.
capital :: String -> String
capital name = case name of
"" -> "Empty string, whoops!"
all@(x:xs) -> "The first letter of " ++ all ++ " is " ++ [x]
Note that indent spacing is significant in Haskell but inter-spacing is just for aesthetic e.g. space between ->
above.
Let expression
let <value definitions> in <expression>
allows to define constants which should help more readable code.
cylinder :: (RealFloat a) => a -> a -> a
cylinder r h =
let sideArea = 2 * pi * r * h
topArea = pi * r ^2
in sideArea + 2 * topArea
Guard conditions
Instead of defining a function by pattern matching, we can also declare a function separately by guard conditions. (Examples from http://learnyouahaskell.com/syntax-in-functions)
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"
| weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
otherwise
is just a constant for True
value.
We can also define values to reduce duplication.
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= skinny = "You're underweight, you emo, you!"
| bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= fat = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
skinny = 18.5
normal = 25.0
fat = 30.0
Note that we can use guard condition with pattern matching!
Recursive
factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)
Note: Haskell support Tail Recursive.
Application and Composition
$
(dollar) operator is function application operator which helps clean up parenthesis in function call. Any function call f x
or f (x)
can be written in f $ x
. For example:
x = sum (filter (> 10) (map (*2) [2..10]))
can be written with $
like:
x = sum $ filter (> 10) $ map (*2) [2..10]
.
(dot) operator is function composer, like FoG in math. The above expression can be re-written with .
.
sumSomething = sum . filter (>10) . map (*2)
x = sumSomething [2..10]
-- or
y = sum . filter (>10) . map (*2) $ [2..10]
Conditional Flow
If-then-else
if <condition> then <expression> else <expression>
Note that:
if
is not a statement but it is an expression. e.g.x = if True then 1 else 2
, then x = 1.else
must always be present.
Type Classes
Examples from http://learnyouahaskell.com/making-our-own-types-and-typeclasses
-- define a typeclass Eq
class Eq a where
(==) :: a -> a -> Bool {- just an interface for a function -}
(/=) :: a -> a -> Bool
x == y = not (x /= y) {- default implementation of function == -}
x /= y = not (x == y)
-- some union type
data TrafficLight = Red | Yellow | Green
-- implement Eq over TrafficLight!
instance Eq TrafficLight where
Red == Red = True
Green == Green = True
Yellow == Yellow = True
_ == _ = False
Only ==
function of Eq TrafficLight
is declared because /=
function can use the default implementation.
I/O monad action[1]
Haskell is pure functional language, meaning functions are not allowed side-effect. In order to read or write to console, which is considered as side-effect, these operations are wrapped in a computational context called I/O action (monad), represented by IO <type>
type.
Let's look at print string function putStrLn
, which takes a String
parameter and prints it to console.
putStrLn :: String -> IO ()
The return type of putStrLn
is IO ()
, if you only assign it to a symbol, nothing will happen because it is executed. The only way to execute IO action is to return it from main
program.
(Note: IO action can be also executed from REPL but when writing program only IO that returns from main
get execution)
main :: IO ()
main = putStrLn "Hello, world!"
Introduction to do
block
To print two string in a function, it needs a special construct called do
block. You cannot just simply put two functions into main
.
main =
putStrLn "Hello"
putStrLn "World" -- this simply not work!
(Because putStrLn "Hello"
already returns IO ()
type, which is already fit to main
needs, and is independent to the second putStrLn
function call.)
Think of do
just like {}
block in C-like languages. So the main
function becomes:
main = do
putStrLn "Hello"
putStrLn "World" -- OK!
(Actually, do
block is a magic shortcut of function binding. More explanation in the section below)
Note that do
block is also an expression. It takes output of the last function call as its return type. So the do
block above has return type IO ()
as same as putStrLn
.
Monad Binding/Chain
IO
type is actually a Monad type and it is implemented Monad typeclass, which has a bind function (some may call chain function).
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(forall
is Haskell language extension. Find more information here. Just ignore it for now.)
Bind function takes a monad of type a
and sends its result of type a
to a function (a -> m b)
and finally gets a monad of type b
as the result. Without do
block, we have to use >>=
function to chain two monad calls like following example:
main = putStrLn "Hello" >= (\_ -> putStrLn "World")
-- or 3 text printings...
main = putStrLn "Hello" >= (\_ -> putStrLn "World" >= (\_ -> putStrLn "!!!"))
-- compared with...
main = do
putStrLn "Hello"
putStrLn "World"
putStrLn "!!!"
Read from console input
To read a line of text from console input, use getLine :: IO String
. For example:
main = putStrLn "What's your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn "Hello " ++ name)
-- or using do block
main = do
putStrLn "What's your name?"
name <- getLine
putStrLn "Hello " ++ name
<-
keyword in do
block is to explicit a symbol to retrieve the returned value of an IO
instance.
Also note that getLine
is not a function. It is just an IO monad action.
Language Extensions
Haskell has a ton of language extensions which, when enabled, allows to use a new syntax in the language. Language extension can be enabled by either compiler option, in .cabal
file, or in the source file (.hs
).
To enable in the source file, use {-# LANGUAGE extension1, extension2, ... #-}
to specify needed extensions. Note that LANGUAGE
is a required keyword. For example, to enable OverloadedStrings
extension and TypeFamilies
extension, use:
{-# LANGUAGE OverloadedStrings, TypeFamilies #-}
-- Haskell code body
main :: IO ()
More information about extensions used in this example can be found in following:
- OverloadedStrings - Allow string literal to become other types than
[Char]
. - TypeFamilies - allow declaration of types as a group/family.
Haskell language extension is hard to find a collective document. Best I find is https://wiki.haskell.org/Language_extensions.
Common Functions
Boolean
<bool_expr1> && <bool_expr2>
- short-circuit AND. e.g.True && False
<bool_expr1> || <bool_expr2>
- short-circuit OR. e.g.True || False
<bool_expr1> and <bool_expr2>
- short-circuit group AND. e.g.and [True, True, False]
<bool_expr1> or <bool_expr2>
- short-circuit group OR. e.g.or [True, True, False]
not <expr>
- NOT operator<lhs> == <rhs>
- equality<lhs> /= <rhs>
- in-equality
Character
chr <Number>
- Convert number to a character e.g.chr 32
List
<list1> (++) <list2>
- List concatenation. e.g.[1,2] ++ [3,4]
<element>:<list>
- Link an element to a list. e.g.1 : [2,3,4]
head <list>
- Get head element. e.g.head [1,2,3]
tail <list>
- Get tail element. e.g.tail [1,2,3]
reverse <list>
- Reverse list. e.g.reverse [1,2,3]
length <list>
Tuple
fst (x,y)
- returns first value, which isx
.snd (x,y)
- returns second value, which isy
.
References
- Learn You a Haskell for Great Good!
- Cabal User Guide
newtype
related can be found in http://learnyouahaskell.com/functors-applicative-functors-and-monoids.
I/O monad related topic can be found in http://learnyouahaskell.com/input-and-output. ↩︎