Haskell Tutorial
Act I
1. Introduction
Programming in Haskell is different from the imperative programming you might
be accustomed to. Haskell is a pure functional language, which means it is
based on functions and avoids side effects.
Here are some basics of programming in Haskell:
1. Functions: In Haskell, functions are first-class citizens. This means
you can pass them as arguments to other functions, return them as values from
functions, and store them in data structures.
2. Immutability: Data is immutable by default in Haskell. This means
once a value is defined, it cannot be changed. Instead, functions produce new
values from input data.
3. Lazy Evaluation: Haskell uses lazy evaluation. This means expressions are
only evaluated when their value is needed. This allows writing efficient
programs even with potentially infinite data.
4. Strong Static Types: Haskell is strongly typed, meaning each
expression has a determined type at compile time. This ensures high program
safety and reliability by detecting type errors at compilation rather than at
runtime.
5. Pattern Matching: Haskell offers a powerful pattern matching
mechanism that allows for concise and expressive decomposition of data
structures.
6. Infinite Lists: Thanks to lazy evaluation, Haskell allows
manipulation of infinite lists. You can define an infinite list and only
evaluate the elements needed.
7. Recursion: Like in most functional programming languages, recursion
is a key concept in Haskell. Loops are generally replaced by recursion,
enabling writing functions in a declarative manner.
8. Monads: Monads are a powerful abstraction in Haskell for handling
side effects such as input/output, error handling, or probabilistic
computation. They enable sequencing computations safely and concisely.
In summary, Haskell programming relies on pure functions, strong static types,
lazy evaluation, and other functional concepts to create safe, efficient, and
expressive programs. While it may seem different at first, Haskell offers an
elegant and powerful approach to programming once you get used to it.
2. Function Example
Here's a simple example of a function in Haskell that calculates the sum of elements in a list:
-- Definition of the sumList function which takes a list of integers as input and returns their sum
sumList :: [Int] -> Int -- Function type: takes a list of integers as input and returns an integer
sumList [] = 0 -- Base case: if the list is empty, the sum is 0
sumList (x:xs) = x + sumList xs -- Recursive case: adds the head element of the list to the sum of the remaining list
-- Example of using the function
main = do
let myList = [1, 2, 3, 4, 5]
putStrLn $ "The sum of the list " ++ show myList ++ " is " ++ show (sumList myList)
In this example:
- The function `sumList` takes a list of integers `[Int]`
as input and returns an integer `Int`.
- It uses pattern matching to define two cases:
- If the list is empty (`[]`), the sum is 0.
- Otherwise, the function adds the first element `x` of the list
to the sum of the remaining elements (`xs`), using recursion.
- The usage example in the `main` function defines a list `myList`
and prints the sum of its elements using the `sumList` function.
Compile with ghc :
$ ghc sum.hs
[1 of 1] Compiling Main ( sum.hs, sum.o )
Linking sum ...
$ ./sum
The sum of the list [1,2,3,4,5] is 15
3. Main Data Structures in Haskell
In Haskell, the main data structures include lists, tuples, and algebraic data
types. Here's an overview of each of these data structures:
1. Lists: Lists are ordered collections of elements of the same type. They are
defined using square brackets and can contain zero or more elements. Lists can
be constructed recursively using the cons operator `:` to add an element to an
existing list. For example:
[1, 2, 3, 4]
2. Tuples: Tuples are ordered collections of elements of different types.
Unlike lists, tuples have a fixed size and cannot be modified. They are defined
using parentheses and can contain any number of elements. For example:
(1, "hello", True)
3. Algebraic Data Types: Haskell allows defining custom data types using the
`data` declaration. This enables creating composite data types
containing multiple fields, similar to structures in imperative languages. For example:
data Person = Person String Int
This code creates a new data type `Person` with two fields: a name of type
`String` and an age of type `Int`.
In addition to these basic data structures, Haskell also offers more advanced
data types such as arrays, sets, and trees, which can be defined using
third-party libraries or custom data types.
Here's an example of using the `Person``Person` data type
in Haskell:
-- Definition of the Person data type
data Person = Person String Int
-- Function to display a person
showPerson :: Person -> String
showPerson (Person name age) = "Name: " ++ name ++ ", Age: " ++ show age
-- Example of use
main :: IO ()
main = do
let alice = Person "Alice" 25
ethan = Person "Ethan" 30
putStrLn $ "Informations about Alice : " ++ showPerson alice
putStrLn $ "Informations about Ethan : " ++ showPerson ethan
In this example:
- The `Person` data type is defined with two fields:
`name` of type `String` and
`age` of type `Int`.
- A `showPerson` function is defined to display the information of a person.
- In the `main` function, two persons, Alice and Ethan,
are created using the `Person` constructor.
- Information about each person is displayed using the `showPerson` function.
Executing this program will display information about Alice and Ethan:
Information about Alice: Name: Alice, Age: 25
Information about Ethan: Name: Ethan, Age: 30
This example demonstrates how to define and use a custom data type in Haskell
to represent entities with specific attributes.
4. Main Types in Haskell
In Haskell, data types are a fundamental concept. Here are some of the main data types in Haskell:
1. Basic Types:
- `Int`: Fixed-precision signed integers.
- `Integer`: Unlimited-precision signed integers.
- `Float`: Single-precision floating-point numbers.
- `Double`: Double-precision floating-point numbers.
- `Char`: Unicode characters.
- `Bool`: Boolean values `True` or `False`.
2. Composite Types:
- Lists: Ordered collection of elements of the same type.
- Tuples: Ordered collection of elements of different types.
- Algebraic Data Types: Custom data types defined using the
`data` declaration.
3. Function Types:
- `->`: Function type. For example, `Int -> Int`
represents a function taking an integer input and returning an integer output.
4. Polymorphic Types:
- `a`: Polymorphic variable type. It can represent any type.
- `Maybe a`: Type representing an optional value that can be
`Just a` (with a value) or `Nothing`
(without a value).
5. Complex Types:
- Lists of lists: For example, `[Int]`` represents a list of integers,
`[[Int]]` represents a list of lists of integers, and so on.
- Higher-order Functions: Functions can take other functions as arguments or return them as results.
6. Derived Types:
- New types derived from existing basic or composite types using keywords
such as `type` and `newtype`.
These types form the foundation of programming in Haskell and provide great
expressiveness for defining data structures and functions with a high degree of
safety and precision typical of Haskell's strong static typing.
5. Derived Types with `type` and `newtype`
Here are examples of using the `type` and
`newtype` keywords to create derived types in Haskell:
1. Using `type` :
-- Definition of a derived type to represent usernames
type Username = String
-- Using the derived type
showUsername :: Username -> String
showUsername username = "Username: " ++ username
main :: IO ()
main = do
let user = "JohnDoe"
putStrLn $ showUsername user
In this example, `Username` is a type alias for
`String`. This allows giving additional semantic
meaning to a string without introducing distinct new types.
2. Using `newtype` :
-- Definition of a new derived type to represent user ages
newtype Age = Age Int
-- Function to display an age
showAge :: Age -> String
showAge (Age age) = "Age : " ++ show age
main :: IO ()
main = do
let userAge = Age 30
putStrLn $ showAge userAge
In this example, `Age` is a new derived type from `Int`
using `newtype`. This creates a new type distinct from `Int`
while retaining a similar internal representation. This can be useful for avoiding type errors and improving code
readability.
6. Difference between `type` and `newtype`
In Haskell, `type` is used to create type aliases, while
`newtype` is used to create distinct new types with specific behaviors.
1. Type Synonym (type):
- `type` is used to create type aliases, meaning both types are interchangeable and behave the same way.
- For example, `type UserName = String` creates an alias `UserName` for the
type `String`. Both types are essentially identical and can be used
interchangeably.
- Type synonyms are primarily used to make the code more readable and to express the developer's intent.
2. Newtype:
- `newtype` is used to create a distinct new type, even if it's based on an existing type.
- It allows defining type instances and behaviors specific to this new type without having
to worry about instances of the underlying type.
- For example, `newtype Meter = Meter Double` creates a new type
`Meter` that is distinct from `Double`,
even though it has the same underlying representation.
- Newtypes offer additional safety as they prevent type errors by ensuring different types won't be accidentally mixed.
In summary, `type` creates a type alias, while `newtype`
introduces a distinct new type with specific properties, thus providing more safety and clarity in
the code.
7. `Maybe` Type
`Maybe` is a data type in Haskell used to represent
values that can be present or absent. It's an elegant way to handle cases where
a value may be undefined or null, while avoiding null reference errors.
In simple terms, `Maybe` can be either:
1. `Just a`: representing an existing value of type `a`.
2. `Nothing`: representing the absence of a value.
For example, suppose you have a function that searches for an element in a list
and returns that element if found. If the element is not found, the function
could return `Nothing` to indicate that no matching value was found.
Here's an example of a function using `Maybe`:
safeDivide :: Double -> Double -> Maybe Double
safeDivide _ 0 = Nothing
safeDivide x y = Just (x / y)
This `safeDivide` function divides two numbers, but it
returns `Nothing` if the second number is zero, thus
avoiding a division by zero error.
Here's how you can use this function:
main :: IO ()
main = do
putStrLn "Enter two numbers:"
num1 <- readLn
num2 <- readLn
case safeDivide num1 num2 of
Just result -> putStrLn $ "Result of division: " ++ show result
Nothing -> putStrLn "Cannot divide by zero!"
This use of `Maybe` allows for safe and concise handling
of potentially missing values, by forcing the developer to explicitly handle
cases where a value may be absent. This encourages robust programming and
proper handling of null values.
8. `$` Operator
In Haskell, the symbol `$` is called "function application operator" or
"low-precedence operator". It is used to avoid writing unnecessary parentheses
when applying functions.
Here's how it works:
1. `f $ x` is equivalent to `f x`.
2. It has low precedence, meaning any expression to its right is evaluated before the function application to its left.
3. It can be used to avoid excessive parentheses, especially when applying nested functions.
Here's an example to illustrate its usage:
-- Both of the following expressions are equivalent:
-- Without $
result1 = sum (map (+1) [1, 2, 3])
-- With $
result2 = sum $ map (+1) [1, 2, 3]
In this example, `sum $ map (+1) [1, 2, 3]` is equivalent to
`sum (map (+1) [1, 2, 3])`. The `$` operator avoids extra parentheses
around `map (+1) [1, 2, 3]`, making the code more readable and avoiding
parenthesizing errors.
9. The `foldl` Function, Left Fold Function
The `foldl` function in Haskell is a standard library function that
takes a function, an initial element (also known as an accumulator), and a list
as arguments, and then iteratively applies the function to each element of the
list, using the initial element as the first argument of the function, and then
passing it successively at each iteration.
Here's a simple presentation of the `foldl` function:
Here's its signature:
foldl :: (b -> a -> b) -> b -> [a] -> b
It takes a binary function `b -> a -> b` (where `b`
is the type of the accumulator and `a` is the type of the list elements), an
initial value of the accumulator of type `b`, a list `[a]`,
and then applies the binary function from left to right on each element of the list,
accumulating, or passing the result into the accumulator, which can also be an
intermediate result.
Here's an example of usage:
-- Sum of elements in a list
sumList :: [Int] -> Int
sumList xs = foldl (+) 0 xs
-- Concatenation of all elements in a list of strings
concatList :: [String] -> String
concatList xs = foldl (++) "" xs
In `sumList`, the `foldl (+) 0 xs` function accumulates the sum of the elements
of the list `xs` starting from zero as the initial value.
In `concatList`, the `foldl (++) "" xs` function concatenates all the elements
of the list `xs` starting from an empty string as the initial value.
10. The Interactive Interpreter
GHCi, short for "Glasgow Haskell Compiler Interactive", is an interactive
interpreter provided with the GHC (Glasgow Haskell Compiler).
GHCi can be launched by typing `ghci` at the command line:
$ ghci
GHCi, version 8.8.4, :? for help
Prelude> print "hello"
"hello"
Prelude> :type print
print :: Show a => a -> IO ()
Prelude> 2 + 2
4
Prelude> square x = x * x
Prelude> square 6
36
Prelude> :type square
square :: Num a => a -> a
Leaving GHCi.
You can load Haskell source files into GHCi using the `:load` command. For
example, `:load myfile.hs` will load the file `myfile.hs`.
You can ask GHCi to display the type of an expression using the `:type`
command. For example, `:type head` will display the type of the
`head` function.
11. `Num a` Type Class
`Num a` is a type class in Haskell. In Haskell, type classes
are used to define common behaviors shared by multiple types. The `Num`
class represents numeric types, that is, types that support arithmetic operations like addition,
subtraction, multiplication, etc.
Thus, when you see `Num a`, it means that `a`
is a type that is an instance of the `Num` class, meaning it is numeric.
This includes types like `Int`, `Integer`,
`Float`, `Double`, etc.
For example, if a function has the signature `foo :: Num a => a -> a`, it means
that `foo` takes an argument of numeric type `a`
and also returns a result of numeric type `a`, where `a`
can be any numeric type.
12. Type Class Constraints
In Haskell, `=>` is used to specify type class constraints. When you see a
function signature or type with `=>`, it means that the types used in that
signature or type must satisfy the specified type class constraints.
For example, if you see a function signature like this:
foo :: Num a => a -> a
It means that `foo` is a function taking an argument of type
`a`, where `a` must be an instance of the
`Num` type class (a numeric type), and also returning a result of type
`a` which must be the same numeric type.
In short, `=>` is used to impose constraints on the types
used in function or type signatures. This ensures that the operations specified in the
type class constraints are available for the types used.
13. Initializing a List of 12 Random Numbers
To initialize a list of 12 random numbers in Haskell, you can use the
`System.Random` module to generate random numbers and the
`Control.Monad` module to import the `replicateM`
function. Here’s how you can do it:
import System.Random
import Control.Monad (replicateM)
-- Generate a list of 12 random numbers between 0 and 100 inclusive
randomNumbers :: IO [Int]
randomNumbers = replicateM 12 $ randomRIO (0, 100)
In this example:
- `randomRIO` is a function that generates a random number within the specified range.
- `replicateM` is a function that repeats an action a certain number of times and collects the results into a list.
- Le résultat est une liste de 12 nombres aléatoires entre 0 et 100 inclus.
- The result is a list of 12 random numbers between 0 and 100 inclusive.
You can use this function in your program to get a list of random numbers by
simply calling `randomNumbers`.
For example:
main :: IO ()
main = do
numbers <- randomNumbers
print numbers
This will display a list of 12 random numbers each time you run your Haskell program.
$ ghc r.hs -package random
[1 of 1] Compiling Main ( r.hs, r.o )
Linking r ...
$ ./r
[37,99,100,64,28,93,67,53,60,25,33,19]
14. Some Basic List Processing Functions
$ ghci
GHCi, version 8.8.4, :? for help
Prelude> li = [1,2,3,4,5,6]
Prelude> take 2 li
[1,2]
Prelude> drop 2 li
[3,4,5,6]
Prelude> reverse li
[6,5,4,3,2,1]
Prelude> map (+ 2) li
[3,4,5,6,7,8]
Prelude> foldl (\acc x -> acc + x) 0 li
21
Prelude> filter (\x -> (x `mod` 2) == 0) li
[2,4,6]
Prelude> 5 `elem` li -- Testing for the presence of an element in a list
True
Prelude> 12 `elem` li
False
Prelude> length li
6
Prelude> [0, 100] ++ li
[0,100,1,2,3,4,5,6]
Prelude> import Data.List (find)
Prelude Data.List> find (\x -> x == 4) li
Just 4
Prelude Data.List> :type find
find :: Foldable t => (a -> Bool) -> t a -> Maybe a
15. Naming Fields in a `data` Structure
In Haskell, with `data`, you can also name the fields in each
data constructor. This allows you to create data types with named fields to improve the
readability and maintainability of your code.
Here is an example of a data type declaration with named fields:
data Person = Person { firstName :: String,
lastName :: String,
age :: Int,
email :: String
}
In this example, `Person` is a data type with a single data constructor named
`Person`. This data constructor has four named fields:
`firstName`, `lastName`,
`age`, and `email`, each with its own type.
Using named fields allows you to easily access the values associated with each
field in an instance of `Person`. For example:
let john = Person { firstName = "John", lastName = "Doe", age = 30, email = "john@example.com" }
You can then access the individual fields like this:
print (firstName john) -- Prints "John"
print (age john) -- Prints 30
Using named fields in `data` types is a convenient way to enhance the clarity
and maintainability of your Haskell code.
16. Structure Update
Here is how to create a function that returns a new `data` structure with one
field having a different value from the original:
$ ghci
GHCi, version 8.8.4, :? for help
Prelude> data Person = Person { firstName :: String, lastName :: String, age :: Int } deriving (Show)
Prelude> let john1 = Person { firstName = "John", lastName = "Doe", age = 30 }
Prelude> newAge person _age = person { age = _age }
Prelude> let john2 = newAge john1 31
Prelude> john2
Person {firstName = "John", lastName = "Doe", age = 31}
`deriving (Show)` allows the type to be displayed.
17. Algebraic Data Types
In Haskell, it is possible to use to define data
types that can take different forms.
Here is an example illustrating this usage with the `data` keyword:
data Shape
= Circle Float
| Rectangle Float Float
| Triangle Float Float Float
This definition allows creating values of the `Shape` type, which can be a
`Circle` with a single `Float` argument, a
`Rectangle` with two `Float` arguments,
or a `Triangle` with three `Float` arguments.
Using Values
To create and use values of this type in Haskell:
let myCircle = Circle 5.0
area :: Shape -> Float
area shape = case shape of
Circle r -> 3.14 * r * r
Rectangle w h -> w * h
Triangle a b c -> -- area of a triangle
let s = (a + b + c) / 2
in sqrt (s * (s - a) * (s - b) * (s - c))
myTriangle :: Shape
myTriangle = Triangle 3.0 4.0 5.0
let triangleArea = area myTriangle
(In the `Shape` type definition for a triangle, the three values represent the
lengths of the three sides of the triangle.)
Algebraic Data Types allow you to define data types that can have multiple
constructors with different arguments or no arguments at all.
data Dir = Left | Right
18. Extracting a sub-string
$ ghci
GHCi, version 8.8.4, :? for help
Prelude> let subStr s i j = take j (drop i s)
Prelude> let s = "Hello World"
Prelude> subStr s 0 5
"Hello"
Prelude> subStr s 3 5
"lo Wo"
Prelude> :t subStr
subStr :: [a] -> Int -> Int -> [a]
Prelude> :t s
s :: [Char]
19. Tail Recursion
Tail recursion is important in Haskell because it helps prevent stack overflow
when executing recursive functions on large inputs. In Haskell, tail recursion
is often automatically optimized by the compiler through tail call
optimization. However, this optimization only occurs for functions that are
written in a particular form known as "tail recursion."
To benefit from this optimization, you must ensure that the recursive call is
the last operation performed in the function and that there are no additional
operations to perform after the recursive call.
Example
-- not tail-rec
sumList1 li =
case li of
[] -> 0
x:xs -> x + sumList1 xs
-- tail-rec
sumList2 li =
let aux [] sum = sum
aux (x:xs) sum = aux xs (x + sum)
in aux li 0
main = do
let li = [1,2,3,4,5,6]
print $ sumList1 li
print $ sumList2 li
In the `sumList1` function, the recursive call `sumList1 xs`
must be completed before the addition with `x` can occur.
Therefore, the recursive call is not in the tail position.
In contrast, in the `sumList2` function, there are no
operations left to perform after the recursive call. The addition is performed
by passing an additional parameter, which is returned at the end of all
recursive calls.
20. Haskell Indentation
In Haskell, indentation is crucial for defining the structure of the code.
Incorrect indentation can lead to syntax errors. Code blocks are not grouped by
braces or keywords like in other languages. Instead, lines of code that belong
to the same block must be indented at the same level (similar to Python). This
enhances readability, ensures syntax consistency, and helps clarify the
program's structure.
21. Indexing Operator
In Haskell, the `!!` operator is used to access an element of a list by its
index. This operator takes a list and an index as arguments and returns the
element located at that index. Indexing starts at 0, meaning that index 0
corresponds to the first element of the list.
Here's an example of using `!!`:
ghci> let list = [10, 20, 30, 40, 50]
ghci> list !! 0
10
ghci> list !! 2
30
ghci> list !! 4
50
In this example, `list !! 0` returns `10`,
which is the first element of the list. `list !! 2` returns `30`,
which is the third element of the list, and so on.
Technical Details:
- Signature: The `!!` operator has the signature
`(!!) :: [a] -> Int -> a`.
This means it takes a list of type `[a]` and an
`Int` integer, and returns an element of type
`a`.
- Safety: If the index is out of bounds of the list, it triggers a
runtime error. For example:
ghci> list !! 5
*** Exception: Prelude.!!: index too large
- Performance: Accessing an element via `!!`
has a time complexity of (O(n)) where (n)
is the position of the element, as it needs to traverse the list up to the specified index.
This can be inefficient for long lists.
Example Code
Here's an example Haskell program using `!!`:
main :: IO ()
main = do
let numbers = [1, 2, 3, 4, 5]
putStrLn $ "The first element is: " ++ show (numbers !! 0)
putStrLn $ "The third element is: " ++ show (numbers !! 2)
putStrLn $ "The fifth element is: " ++ show (numbers !! 4)
This program will output:
The first element is: 1
The third element is: 3
The fifth element is: 5
Alternatives
For more performant data structures where fast access to an element by index is
needed, other data structures like vectors (`Vector`)
can be considered, which offer (O(1)) access:
import qualified Data.Vector as V
main :: IO ()
main = do
let numbers = V.fromList [1, 2, 3, 4, 5]
putStrLn $ "The first element is: " ++ show (numbers V.! 0)
putStrLn $ "The third element is: " ++ show (numbers V.! 2)
putStrLn $ "The fifth element is: " ++ show (numbers V.! 4)
By using `Data.Vector`, access to elements by index is
much more efficient.
22. The `IO ()` Type
In Haskell, the type `IO ()` is used to represent input/output
actions that do not produce a meaningful value.
1. `IO`: It's a type that encapsulates input/output actions.
A value of type `IO a` represents an action that, when executed,
can interact with the outside world (like reading a file, printing to the screen, etc.)
and produces a value of type `a`.
2. `()`, the unit type: It represents a single
value, also known as unit, often used to indicate that there's no meaningful
value produced. It's somewhat like `void` in other
programming languages.
When you combine the two to get `IO ()`, it means we have
an input/output action that, when executed, doesn't produce a meaningful value (or,
more precisely, produces the value `()`, which is trivial
and uninteresting in itself).
Examples
Here's a simple example:
main :: IO ()
main = putStrLn "Hello, World!"
In this code:
- `putStrLn "Hello, World!"` is an input/output action
that prints a string to the screen.
- `main` has the type `IO ()`,
which means it's an input/output action that, once executed, doesn't produce
a meaningful value (the return value is `()`).
Why `IO ()`?
The type `IO ()` is useful for actions where the effect of the
action is important (such as writing to a file or displaying a message), but the value
produced by this action is not used. In other words, `IO ()`
means that we are interested in the side effects of the action, not in a return value.
Managing Side Effects
`IO` actions are essential in Haskell for managing side effects
while keeping the language pure. Pure functions have no side effects, which allows for
mathematical reasoning about programs. By isolating side effects in `IO`
actions, Haskell maintains this purity.
Example with Combined Effects
Here is another example that shows a combination of several `IO ()` actions:
main :: IO ()
main = do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ "!")
In this code:
- `putStrLn` and `getLine` are input/output actions.
- The sequence of these actions is combined using `do` notation.
- The type of `main` is still `IO ()`
because the final value produced is `()`.
In summary, `IO ()` in Haskell is a type used to represent
input/output actions that do not have a significant return value but perform important
operations by interacting with the outside world.
23. Exceptions
In Haskell, exceptions are used to handle exceptional situations or errors in a
program. Here's how they work:
1. Raising an Exception: To signal an error or an exceptional situation, you
can raise an exception using the `throw` function from the
`Control.Exception` module. For example:
-- Fonction de division
divide :: Int -> Int -> Int
divide _ 0 = throw DivideByZero
divide x y = x `div` y
Here, if the second argument of the `divide` function is 0,
a `DivideByZero` exception is raised.
2. Catching an Exception: To catch and handle an exception, you can use the
`catch` function also provided by
`Control.Exception`. It takes two arguments:
an action to execute and an exception-handling function.
import Control.Exception (ArithException(..), tryJust, evaluate, throw)
import Control.Monad (guard)
-- Put the divide function here.
main :: IO ()
main = do
-- Attempting to divide by zero.
result <- tryJust (\e -> guard (e == DivideByZero) >> return e) (evaluate (divide 10 0))
case result of
Left DivideByZero -> putStrLn "Division par zéro !"
Right value -> putStrLn $ "Result: " ++ show value
Here, `tryJust` allows you to specifically catch the
`DivideByZero` exception, and the `evaluate`
function lets you evaluate the expression, raising an exception if necessary.
3. Types of Exceptions: In Haskell, exceptions are typed. This means
that each type of exception is associated with a specific type. For example,
`DivideByZero` is a type of exception that represents
a division by zero.
4. Safe Exception Handling: Exceptions in Haskell are handled in a pure and
safe manner. This means that exceptions cannot be used for unsafe operations as
in other programming languages. Exceptions in Haskell are primarily used for
error handling within the context of safe functional programming.
Another example with `catch`:
import Control.Exception (ArithException(..), catch, evaluate)
-- Division function
divide :: Int -> Int -> Int
divide x y = x `div` y
main :: IO ()
main = do
putStrLn "Attempting to divide by zero."
-- Evaluating the division and handling the exception
(evaluate (divide 10 0) >>= print) `catch` handler
-- Exception handler
handler :: ArithException -> IO ()
handler DivideByZero = putStrLn "Caught exception: Division by zero"
handler exn = putStrLn $ "Caught another arithmetic exception: " ++ show exn
In summary, exceptions in Haskell provide a safe and efficient mechanism for
handling errors and exceptional situations in Haskell programs, clearly
distinguishing them from the normal flow of control.
24. Installing Haskell Libraries
To install a Haskell library, you can use your distribution's package manager
or the Haskell package manager called `cabal`.
Here are the steps to follow:
$ sudo apt-get update
$ sudo apt-cache search ghc
ghc - The Glasgow Haskell Compilation system
$ sudo apt-get install ghc
$ sudo apt-cache search cabal
cabal-install - command-line interface for Cabal and Hackage
$ sudo apt-get install cabal-install
$ cabal update
Downloading the latest package list from hackage.haskell.org
$ cabal install --lib gloss
Hackage is the repository of Haskell libraries.
Act II — Scene 1
25. Hello World with SDL2
Here is a minimal example using the SDL2 graphics library,
which opens a window and displays a rectangle:
{-# LANGUAGE OverloadedStrings #-}
import SDL.Vect (V2(..), V4(..), Point(P))
import SDL (($=))
import qualified SDL
import Control.Monad (unless)
main :: IO ()
main = do
SDL.initialize [SDL.InitVideo]
window <- SDL.createWindow "Hello World" SDL.defaultWindow { SDL.windowInitialSize = V2 320 240 }
SDL.showWindow window
renderer <- SDL.createRenderer window (-1) SDL.defaultRenderer
loop renderer
SDL.destroyRenderer renderer
SDL.destroyWindow window
SDL.quit
loop :: SDL.Renderer -> IO ()
loop renderer = do
SDL.rendererDrawColor renderer $= V4 0 0 255 255
SDL.clear renderer
SDL.rendererDrawColor renderer $= V4 0 0 0 255
let squareRect = SDL.Rectangle (P (V2 80 60)) (V2 60 40) -- pos:(80, 60), size:(60x40)
--SDL.drawRect renderer (Just squareRect)
SDL.fillRect renderer (Just squareRect)
SDL.present renderer
event_loop renderer
event_loop :: SDL.Renderer -> IO ()
event_loop renderer = do
maybeEvent <- SDL.pollEvent
case maybeEvent of
Just event -> unless (SDL.eventPayload event == SDL.QuitEvent) (event_loop renderer)
Nothing -> loop renderer
Installing SDL2 for Haskell:
$ sudo apt-cache search sdl2
libghc-sdl2-dev - high- and low-level bindings to the SDL 2 library
$ sudo apt-get install libghc-sdl2-dev
Compiling the example:
$ ghc hello.hs -package sdl2
These examples cover the basics of Haskell; you can now experiment and try to go further!
You can find various examples (with equivalents in other languages) on the site
RosettaCode.org.
Created with ChatGPT