Quick glance at Haskell for Experienced Developers

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!

Command Line Tools

  • ghc - Compiler
  • ghci - REPL
  • cabal - Package manager
  • runhaskell - 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] or String - list of characters e.g. "Hello"
  • [t] Data.List - list of type t e.g. list of Int: Either [1,2,3], [1..3], or 1: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 representation
  • (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 of void/unit type).

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:

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 is x.
  • snd (x,y) - returns second value, which is y.

References


  1. I/O monad related topic can be found in http://learnyouahaskell.com/input-and-output. ↩︎