h4life: Add.
This commit is contained in:
parent
c3f9ac6aee
commit
863c210fae
16 changed files with 1510 additions and 2 deletions
BIN
content/courses/h4life/h4life-01.pdf
Normal file
BIN
content/courses/h4life/h4life-01.pdf
Normal file
Binary file not shown.
BIN
content/courses/h4life/h4life-02.pdf
Normal file
BIN
content/courses/h4life/h4life-02.pdf
Normal file
Binary file not shown.
BIN
content/courses/h4life/h4life-03.pdf
Normal file
BIN
content/courses/h4life/h4life-03.pdf
Normal file
Binary file not shown.
BIN
content/courses/h4life/h4life-04.pdf
Normal file
BIN
content/courses/h4life/h4life-04.pdf
Normal file
Binary file not shown.
BIN
content/courses/h4life/h4life-exam.pdf
Normal file
BIN
content/courses/h4life/h4life-exam.pdf
Normal file
Binary file not shown.
214
content/courses/h4life/h4life-funcs.hs
Normal file
214
content/courses/h4life/h4life-funcs.hs
Normal file
|
@ -0,0 +1,214 @@
|
|||
{- funcs.hs
|
||||
|
||||
This file contains some of the examples to be shown in the first class
|
||||
of the course "Haskell for Life", by Sergiu Ivanov (sivanov@lacl.fr):
|
||||
|
||||
http://lacl.fr/~sivanov/doku.php?id=en:haskell_for_life
|
||||
|
||||
This file is distributed under the Creative Commons Attribution Alone
|
||||
licence.-}
|
||||
|
||||
|
||||
{- The examples shown in the first part. -}
|
||||
|
||||
add x y = x + y
|
||||
|
||||
factorial :: Int -> Int
|
||||
factorial x = if x == 0
|
||||
then 1
|
||||
else x * factorial (x-1)
|
||||
|
||||
fibonacci :: Int -> Int
|
||||
fibonacci n = if n == 1
|
||||
then 1
|
||||
else if n == 2
|
||||
then 1
|
||||
else fibonacci (n-1)
|
||||
+ fibonacci (n-2)
|
||||
|
||||
myNot :: Bool -> Bool
|
||||
myNot x = if x == True then False else True
|
||||
|
||||
myNot1 :: Bool -> Bool
|
||||
myNot1 True = False
|
||||
myNot1 False = True
|
||||
|
||||
factorial1 :: Int -> Int
|
||||
factorial1 0 = 1
|
||||
factorial1 n = n * factorial1 (n-1)
|
||||
|
||||
fibonacci1 :: Int -> Int
|
||||
fibonacci1 1 = 1
|
||||
fibonacci1 2 = 1
|
||||
fibonacci1 n = fibonacci1 (n-1) + fibonacci1 (n-2)
|
||||
|
||||
fibonacci2 :: Int -> Int
|
||||
fibonacci2 n | n <= 2 = 1
|
||||
fibonacci2 n | n > 2 = fibonacci2 (n-1) + fibonacci2 (n-2)
|
||||
|
||||
myHead :: [a] -> a
|
||||
myHead (h:_) = h
|
||||
|
||||
myTail :: [a] -> [a]
|
||||
myTail (_:xs) = xs
|
||||
|
||||
|
||||
{- The fun starts here.
|
||||
|
||||
This section contains implementations of the functions from Data.List
|
||||
and Prelude. Our implementation of the function foo is called
|
||||
myFoo.-}
|
||||
|
||||
-- | Check whether the list is empty.
|
||||
myNull :: [a] -> Bool
|
||||
myNull [] = True
|
||||
myNull _ = False
|
||||
|
||||
-- | Compute the length of the list.
|
||||
myLength :: [a] -> Int
|
||||
myLength [] = 0
|
||||
myLength (x:xs) = 1 + length xs
|
||||
|
||||
-- | Get the last element of the list.
|
||||
--
|
||||
-- list = myInit list ++ [myLast list]
|
||||
myLast :: [a] -> a
|
||||
myLast [x] = x
|
||||
myLast (x:xs) = myLast xs
|
||||
|
||||
-- | Get all elements of the list, but the last one.
|
||||
--
|
||||
-- list = myInit list ++ [myLast list]
|
||||
myInit :: [a] -> [a]
|
||||
myInit [x] = []
|
||||
myInit (x:xs) = x : myInit xs
|
||||
|
||||
-- | append = (++)
|
||||
append :: [a] -> [a] -> [a]
|
||||
append [] ys = ys
|
||||
append (x:xs) ys = x:append xs ys
|
||||
|
||||
|
||||
-- | Reverses the list.
|
||||
--
|
||||
-- In typical functional implementations, '(++)' needs to traverse the
|
||||
-- first list entirely (cf. 'append'). So this implementation of
|
||||
-- "reverse" would need to traverse the list at every recursive call.
|
||||
-- The overall complexity would thus be O(n^2/2).
|
||||
--
|
||||
-- In Haskell, '(++)' is implemented with optimisation and may take
|
||||
-- constant time to run.
|
||||
badReverse :: [a] -> [a]
|
||||
badReverse [] = []
|
||||
badReverse (x:xs) = badReverse xs ++ [x]
|
||||
|
||||
-- | Reverses the list.
|
||||
--
|
||||
-- This implementation only traverses the list once.
|
||||
myReverse :: [a] -> [a]
|
||||
myReverse xs = reverse' xs []
|
||||
where reverse' [] acc = acc
|
||||
reverse' (x:xs) acc = reverse' xs (x:acc)
|
||||
|
||||
|
||||
myTake :: Int -> [a] -> [a]
|
||||
myTake _ [] = []
|
||||
myTake 0 _ = []
|
||||
myTake n (x:xs) = x:myTake (n-1) xs
|
||||
|
||||
myDrop :: Int -> [a] -> [a]
|
||||
myDrop _ [] = []
|
||||
myDrop 0 xs = xs
|
||||
myDrop n (x:xs) = myDrop (n-1) xs
|
||||
|
||||
|
||||
-- | Glues two lists together.
|
||||
--
|
||||
-- myZip [1,2] [3,4] == [(1,2),(3,4)]
|
||||
myZip :: [a] -> [b] -> [(a,b)]
|
||||
myZip [] _ = []
|
||||
myZip _ [] = []
|
||||
myZip (x:xs) (y:ys) = (x,y):myZip xs ys
|
||||
|
||||
-- | Checks whether an element is part of the list.
|
||||
--
|
||||
-- 'Eq a =>' requires that objects of type 'a' can be "compared".
|
||||
--
|
||||
-- We add two pattern guards to handle the cases when 'y == x' and
|
||||
-- 'y /= x'. 'otherwise' is a condition which is always true.
|
||||
myElem :: Eq a => a -> [a] -> Bool
|
||||
myElem _ [] = False
|
||||
myElem y (x:xs) | y == x = True
|
||||
| otherwise = myElem y xs
|
||||
|
||||
|
||||
-- | Given a function and a list, returns a list containing the
|
||||
-- elements for which the function is true.
|
||||
--
|
||||
-- myFilter odd [1..5] == [1,3,5].
|
||||
--
|
||||
-- 'odd' is the function returning 'True' for odd numbers.
|
||||
myFilter :: (a -> Bool) -> [a] -> [a]
|
||||
myFilter _ [] = []
|
||||
myFilter p (x:xs) | p x = x:myFilter p xs
|
||||
| otherwise = myFilter p xs
|
||||
|
||||
|
||||
-- | Applies a function to all elements of a list and returns the
|
||||
-- results.
|
||||
myMap :: (a -> b) -> [a] -> [b]
|
||||
myMap _ [] = []
|
||||
myMap f (x:xs) = f x:myMap f xs
|
||||
|
||||
myFoldr :: (a -> b -> b) -> b -> [a] -> b
|
||||
myFoldr _ r0 [] = r0
|
||||
myFoldr f r0 (x:xs) = f x (myFoldr f r0 xs)
|
||||
|
||||
|
||||
{- This section contains the reimplementations of some of the
|
||||
functions from Data.List and the standard Prelude using folds. Our
|
||||
implementation of the function foo is called fldFoo.-}
|
||||
|
||||
-- | Sums up the elements of the list.
|
||||
fldSum :: [Int] -> Int
|
||||
fldSum xs = foldl (+) 0 xs
|
||||
|
||||
-- | Checks whether the given function returns 'True' for all elements
|
||||
-- of the given list.
|
||||
fldAll :: (a -> Bool) -> [a] -> Bool
|
||||
fldAll f xs = foldl (\r x -> (f x) && r) True xs
|
||||
|
||||
-- | Checks whether the given function returns 'True' for at least one
|
||||
-- element of the list.
|
||||
fldAny :: (a -> Bool) -> [a] -> Bool
|
||||
fldAny f xs = foldl (\r x -> (f x) || r) False xs
|
||||
|
||||
-- | Concatenates all the lists in the given list.
|
||||
--
|
||||
-- [[1,2],[3,4]] = [1,2,3,4]
|
||||
fldConcat :: [[a]] -> [a]
|
||||
fldConcat xs = foldl (++) [] xs
|
||||
|
||||
|
||||
fldFilter :: (a -> Bool) -> [a] -> [a]
|
||||
fldFilter f xs = reverse (foldl test [] xs)
|
||||
where test r x | f x = x:r
|
||||
| otherwise = r
|
||||
|
||||
fldMap' :: (a -> b) -> [a] -> [b]
|
||||
fldMap' f xs = reverse (foldl (\r x -> f x:r) [] xs)
|
||||
|
||||
|
||||
-- The clever Pentalog guy showed me this solution.
|
||||
fldMap :: (a -> b) -> [a] -> [b]
|
||||
fldMap f = foldr ((:) . f) []
|
||||
|
||||
|
||||
|
||||
-- Bubble sort.
|
||||
bubblesort :: Ord a => [a] -> [a]
|
||||
bubblesort xs = iterate bubble xs !! (length xs - 1)
|
||||
where bubble [] = []
|
||||
bubble [x] = [x]
|
||||
bubble (x:y:xs) | x < y = x : bubble (y:xs)
|
||||
| otherwise = y : bubble (x:xs)
|
575
content/courses/h4life/h4life-monads.hs
Normal file
575
content/courses/h4life/h4life-monads.hs
Normal file
|
@ -0,0 +1,575 @@
|
|||
{- monads.hs
|
||||
|
||||
This file contains the examples on monads to be shown in the third
|
||||
class of the course "Haskell for Life", by Sergiu Ivanov
|
||||
(sivanov@lacl.fr):
|
||||
|
||||
http://lacl.fr/~sivanov/doku.php?id=en:haskell_for_life
|
||||
|
||||
This file is meant to be read sequentially.
|
||||
|
||||
This file is distributed under the Creative Commons Attribution Alone
|
||||
licence.-}
|
||||
|
||||
-- This line imports the (<>) operator from the module 'Data,Monoid'
|
||||
-- (we will need faaar below for the MyWriter monad).
|
||||
import Data.Monoid((<>))
|
||||
|
||||
-- | We will cheat and use these meaningless type synonyms.
|
||||
type Person = String
|
||||
type Car = String
|
||||
|
||||
personByName :: String -> Maybe Person
|
||||
personByName p = Just p
|
||||
|
||||
carByPerson :: Person -> Maybe Car
|
||||
carByPerson p = Just (p ++ "'s car")
|
||||
|
||||
model :: Car -> Maybe String
|
||||
model c = Just ("model of " ++ c)
|
||||
|
||||
modelFromName :: String -> Maybe String
|
||||
modelFromName name = do
|
||||
person <- personByName name
|
||||
car <- carByPerson person
|
||||
model car
|
||||
|
||||
|
||||
|
||||
{- In this section we will reconstruct the Maybe monad.-}
|
||||
|
||||
data Perhaps a = Only a | Nope
|
||||
deriving Show
|
||||
|
||||
instance Monad Perhaps where
|
||||
-- | return :: a -> Perhaps a
|
||||
--
|
||||
-- This implementation is quite natural: we just put the value we
|
||||
-- get inside an instance of 'Perhaps', and we cannot actually
|
||||
-- implement it differently. Indeed, since we don't know anything
|
||||
-- about the type 'a', we cannot construct values of that type, nor
|
||||
-- can we transform them.
|
||||
--
|
||||
-- ATTENTION! 'return' does not abort execution of any block of code
|
||||
-- (as in some other languages)! It's just a function called
|
||||
-- "return".
|
||||
return x = Only x
|
||||
|
||||
-- | (>>=) :: Perhaps a -> (a -> Perhaps b) -> Perhaps b
|
||||
Nope >>= f = Nope
|
||||
(Only x) >>= f = f x
|
||||
|
||||
-- | If you try to compile the file now, GHC will complain that there
|
||||
-- is no instance for 'Applicative Perhaps'; that instance is required
|
||||
-- by the definition of 'Monad'. Let's try defining one, just looking
|
||||
-- at the types of the functions.
|
||||
instance Applicative Perhaps where
|
||||
-- | pure :: a -> Perhaps
|
||||
--
|
||||
-- Wait a second, that's the type of return!
|
||||
pure = return
|
||||
|
||||
-- | (<*>) :: Perhaps (a -> b) -> Perhaps a -> Perhaps b
|
||||
--
|
||||
-- OK, so we have a function (perhaps), and we need to apply to
|
||||
-- (perhaps) a value and then return the result (perhaps). Let's do
|
||||
-- that.
|
||||
--
|
||||
-- Note that we don't use the fact that we work with Perhaps, since
|
||||
-- only the definitions coming from our instance of 'Monad' are
|
||||
-- enough. This means that we will be able to copy this definition
|
||||
-- as-is for the other monads we will construct.
|
||||
u <*> v = do -- This means we do our stuff within a monad.
|
||||
f <- u -- Extract the function.
|
||||
x <- v -- Extract the value.
|
||||
return (f x) -- Feed the value to the function and pack the
|
||||
-- result.
|
||||
|
||||
-- | Oh noes, we still cannot compile this code: no instance for
|
||||
-- 'Functor Perhaps'. Well, 'Functor' seems easy enough.
|
||||
instance Functor Perhaps where
|
||||
-- | fmap :: (a -> b) -> Perhaps a -> Perhaps
|
||||
--
|
||||
-- Hey, that's simple enough!
|
||||
--
|
||||
-- fmap f Nope = Nope
|
||||
-- fmap f (Only x) = Only (f x)
|
||||
--
|
||||
-- This code is specific to 'Perhaps', however. We could perhaps
|
||||
-- (pun intended) re-use our definition of 'Monad' once again:
|
||||
--
|
||||
-- fmap f v = do
|
||||
-- x <- v -- Unpack 'v'.
|
||||
-- return (f x) -- Feed 'x' to f and pack back the result.
|
||||
--
|
||||
-- Yet, we will use the following cool definition based on the
|
||||
-- definition from the instance of 'Applicative'. It's more compact
|
||||
-- and is nice and simple exercise in Haskell types.
|
||||
fmap f x = pure f <*> x
|
||||
|
||||
-- Let's check now that our 'Perhaps' is similar to 'Maybe', as we
|
||||
-- wanted.
|
||||
personByName' :: String -> Perhaps Person
|
||||
personByName' p = Only p
|
||||
|
||||
carByPerson' :: Person -> Perhaps Car
|
||||
carByPerson' p = Only (p ++ "'s car")
|
||||
|
||||
model' :: Car -> Perhaps String
|
||||
model' c = Only ("model of " ++ c)
|
||||
|
||||
modelFromName' :: String -> Perhaps String
|
||||
modelFromName' name = do
|
||||
person <- personByName' name
|
||||
car <- carByPerson' person
|
||||
model' car
|
||||
|
||||
|
||||
|
||||
{- In this section we will reconstruct the list monad.-}
|
||||
|
||||
data List a = Cons a (List a) | Nil
|
||||
deriving Show
|
||||
|
||||
-- | Concatenates two 'List's.
|
||||
concat2 :: List a -> List a -> List a
|
||||
concat2 Nil ys = ys
|
||||
concat2 (Cons x xs) ys = Cons x (concat2 xs ys)
|
||||
|
||||
-- | Concatenates a 'List' of 'Lists'.
|
||||
--
|
||||
-- Example:
|
||||
-- concatLists ( Cons (Cons 1 Nil) (Cons (Cons 2 Nil) Nil) ) == Cons 1 (Cons 2 Nil)
|
||||
concatLists :: List (List a) -> List a
|
||||
concatLists xs = concatAll xs Nil
|
||||
where concatAll (Cons x xs) res = concatAll xs (concat2 res x)
|
||||
concatAll Nil res = res
|
||||
|
||||
-- | Applies the function to all elements of the list and returns the
|
||||
-- list of results.
|
||||
mapList :: (a -> b) -> List a -> List b
|
||||
mapList _ Nil = Nil
|
||||
mapList f (Cons x xs) = Cons (f x) (mapList f xs)
|
||||
|
||||
instance Monad List where
|
||||
-- | return :: a -> List a
|
||||
--
|
||||
-- Create a singleton list out of 'x', just like in the case of
|
||||
-- 'Maybe'.
|
||||
return x = Cons x Nil
|
||||
|
||||
-- (>>=) :: List a -> (a -> List b) -> List b
|
||||
--
|
||||
-- So, we should probably feed the values from 'List a' into the
|
||||
-- function 'a -> List b' one by one (we don't really have any other
|
||||
-- choice). Then we are going to get a bunch of 'List b'; we should
|
||||
-- probably concatenate them to get one single big 'List b'.
|
||||
x >>= f = concatLists (mapList f x)
|
||||
|
||||
-- | And for our 'Applicative' and 'Functor' instances we'll just
|
||||
-- reuse the code we wrote for 'Perhaps'.
|
||||
instance Applicative List where
|
||||
pure = return
|
||||
u <*> v = do
|
||||
f <- u
|
||||
x <- v
|
||||
return (f x)
|
||||
|
||||
instance Functor List where
|
||||
fmap f x = pure f <*> x
|
||||
|
||||
-- | Now let's see what kind of tricks one can do in the list monad.
|
||||
-- We will use Haskell's built-in '[]' for convenience, but exactly
|
||||
-- the same things can be done in `List'.
|
||||
|
||||
-- | Compute the Cartesian product of two lists.
|
||||
--
|
||||
-- We "unpack" first 'xs', then 'ys', and put the obtained 'x' and 'y'
|
||||
-- together. Note that the bind operator '(>>=)' in the list monad
|
||||
-- means that the lambda functions are applied to every element in
|
||||
-- 'xs' and 'ys'. In other words, '(>>=)' is a kind of a foreach
|
||||
-- loop.
|
||||
cartesian :: [a] -> [b] -> [(a,b)]
|
||||
cartesian xs ys = xs >>=
|
||||
(\x -> ys >>=
|
||||
(\y -> return (x,y)))
|
||||
|
||||
-- | Now, let's rewrite the same function using the do notation. In
|
||||
-- this case, every "unpacking" (<-) means a loop over all elements of
|
||||
-- the list. Thus, we loop over all elements of 'xs'; then, for each
|
||||
-- such element, we loop over 'ys' and produce a pair '(x,y)' at each
|
||||
-- iteration.
|
||||
cartesian' :: [a] -> [b] -> [(a,b)]
|
||||
cartesian' xs ys = do
|
||||
x <- xs
|
||||
y <- ys
|
||||
return (x,y)
|
||||
|
||||
|
||||
-- | Return a list of elements for which the given function returns
|
||||
-- 'True'.
|
||||
--
|
||||
-- Note that we do not use return; instead we produce the singleton
|
||||
-- list '[x]' directly. We need that to be able to write '[]' when
|
||||
-- 'x' does satisfy the property.
|
||||
myFilter :: (a -> Bool) -> [a] -> [a]
|
||||
myFilter f xs = do
|
||||
x <- xs
|
||||
if f x then [x] else []
|
||||
|
||||
|
||||
|
||||
{- In this section we will reconstruct a simplified version of the
|
||||
State monad.-}
|
||||
|
||||
-- | Our simplified state monad.
|
||||
data MyState s a = MyState (s -> (a, s))
|
||||
|
||||
-- | This is just a getter for the only field of 'MyState'.
|
||||
runState :: MyState s a -> (s -> (a, s))
|
||||
runState (MyState f) s = f s
|
||||
|
||||
{- In the case of the 'Maybe' ('Perhaps') monad, we wanted to deal
|
||||
with functions returning 'Maybe a'. In the case of state, we want to
|
||||
deal with functions which get state as an argument and which return
|
||||
their result _and also_ the new state. Thus, the functions we work
|
||||
with have types of the form
|
||||
|
||||
'arg1 -> ... -> argn -> state -> (result, state)'.
|
||||
|
||||
Since the bind operator (>>=) will have to pass the state from one
|
||||
function to another one, we cannot just define
|
||||
|
||||
data MyState s a = MyState (a, s)
|
||||
|
||||
If we did that, the bind operator (>>=) would have no way of passing
|
||||
the state to the next function. With our definition of 'MyState' as
|
||||
containing a field of a function type, we will be able to pass around
|
||||
the state.
|
||||
|
||||
The next issue is that 'Monad' can only work with types having one
|
||||
parameter, while 'MyState' has two. This means that we will have to
|
||||
fix one of them. If we fix the parameter 'a' (which is the result of
|
||||
computation), then bind (>>=) should be able to put together two
|
||||
functions returning the same result, but using states of different
|
||||
type, which is not what we want. Therefore, we are going to fix 's',
|
||||
the type of the state.-}
|
||||
|
||||
instance Monad (MyState s) where
|
||||
-- | return :: a -> MyState s a
|
||||
-- return :: a -> (s -> (a, s))
|
||||
--
|
||||
-- We don't want 'return' to tinker with the state; it should just
|
||||
-- put our value into the monad, nothing more. So, it will return a
|
||||
-- function taking the state and returning a tuple containing 'x'
|
||||
-- and _the same_ state.
|
||||
return x = MyState (\s -> (x, s))
|
||||
|
||||
-- | (>>=) :: MyState s a -> (a -> MyState s b) -> MyState s b
|
||||
--
|
||||
-- Now, that's a more difficult type. Without going too much into
|
||||
-- details, let's keep in mind that we want to get the result out of
|
||||
-- 'MyState s a' and put it into the function which is the second
|
||||
-- argument. Now, how do we get the result ('a') out of 'MyState s
|
||||
-- a'? 'MyState s a' is the same thing as 's -> (a,s)', so we could
|
||||
-- get an 'a' if we had an 's', which we don't seem to have...
|
||||
--
|
||||
-- But wait a little! We are supposed to return 'MyState s b',
|
||||
-- which is actually 's -> (b,s)'; that is, we are supposed to
|
||||
-- return a function! And inside this function, we can haz access
|
||||
-- to an instance of 's' which we can use to run the first argument
|
||||
-- ('MyState s a') and get our instance of 'a' to feed into the
|
||||
-- second argument.
|
||||
--
|
||||
-- And yes, a let declaration is just a local binding of values,
|
||||
-- like:
|
||||
--
|
||||
-- let x = 2
|
||||
-- y = 3
|
||||
-- in x + y
|
||||
--
|
||||
-- We can also use pattern matching in let bindings (just like in
|
||||
-- GHCi).
|
||||
x >>= f = MyState (\s ->
|
||||
-- Run 'x' and get the tuple (result,state) in runState (f a) s'
|
||||
let (a,s') = runState x s
|
||||
-- Apply 'f' to 'a', and then run the resulting
|
||||
-- state with 's'.
|
||||
in runState (f a) s')
|
||||
|
||||
-- | Again, a small copy-and-paste work to get our 'Applicative' and
|
||||
-- 'Functor' instances in place.
|
||||
instance Applicative (MyState s) where
|
||||
pure = return
|
||||
u <*> v = do
|
||||
f <- u
|
||||
x <- v
|
||||
return (f x)
|
||||
|
||||
instance Functor (MyState s) where
|
||||
fmap f x = pure f <*> x
|
||||
|
||||
-- | Oh, before we get to write any functions using our 'MyState'
|
||||
-- monad, we have to provide a way to access our state.
|
||||
|
||||
-- | Returns the current state.
|
||||
--
|
||||
-- Remember that unpacking (<-) in a State monad means getting the
|
||||
-- first element of the tuple returned by any function in this monad.
|
||||
-- Thus, the only thing 'myGet' needs to do is put the state it gets
|
||||
-- as an argument into the first component of the tuple.
|
||||
myGet :: MyState s s
|
||||
myGet = MyState (\s -> (s, s))
|
||||
|
||||
-- | Sets the current state.
|
||||
--
|
||||
-- 'myPut' only changes the state and does not return anything
|
||||
-- meaningful; we therefore make it put the empty tuple '()' (unit)
|
||||
-- into the first component of the returned tuple, and we put the new
|
||||
-- state into the second component of the returned tuple.
|
||||
myPut :: s -> MyState s ()
|
||||
myPut s' = MyState (\_ -> ((), s'))
|
||||
|
||||
{- We can now use MyState to implement a small database of cars and
|
||||
people.-}
|
||||
data CarsPeopleDB = CarsPeopleDB [Car] [Person]
|
||||
deriving Show
|
||||
|
||||
cars :: CarsPeopleDB -> [Car]
|
||||
cars (CarsPeopleDB c _) = c
|
||||
|
||||
people :: CarsPeopleDB -> [Person]
|
||||
people (CarsPeopleDB _ p) = p
|
||||
|
||||
-- | This function adds a car to the database.
|
||||
--
|
||||
-- This function can access the database because it is in the
|
||||
-- 'MyState' monad (it returns the type 'MyState <bla bla>'). It just
|
||||
-- adds a car and returns nothing, that's why the '()'.
|
||||
addCar :: Car -> MyState CarsPeopleDB ()
|
||||
addCar c = do
|
||||
db <- myGet -- Get the database.
|
||||
|
||||
let cs = cars db -- Get the list of cars.
|
||||
ps = people db -- Get the list of people.
|
||||
|
||||
let db' = (CarsPeopleDB (c:cs) ps) -- Build the new database.
|
||||
|
||||
myPut db' -- Save the new database.
|
||||
|
||||
-- | Adds a person to the database.
|
||||
--
|
||||
-- Does the same thing as 'addCar', but is written more concisely.
|
||||
addPerson :: Person -> MyState CarsPeopleDB ()
|
||||
addPerson p = do
|
||||
(CarsPeopleDB cs ps) <- myGet
|
||||
myPut (CarsPeopleDB cs (p:ps))
|
||||
|
||||
-- | Checks whether a car is in the database.
|
||||
--
|
||||
-- Now, besides producing the new state, this function should also
|
||||
-- tell us whether the car is in the database or not. That is why the
|
||||
-- second argument to 'MyState' is 'Bool' — it's the answer of the
|
||||
-- function.
|
||||
carKnown :: Car -> MyState CarsPeopleDB Bool
|
||||
carKnown c = do
|
||||
(CarsPeopleDB cs _) <- myGet
|
||||
return (elem c cs) -- Pack the result of checking whether 'c' is an
|
||||
-- element of 'cs'.
|
||||
|
||||
-- | Checks whether a person is in the database.
|
||||
personKnown :: Person -> MyState CarsPeopleDB Bool
|
||||
personKnown p = do
|
||||
(CarsPeopleDB _ ps) <- myGet
|
||||
return (elem p ps)
|
||||
|
||||
|
||||
-- | And now we can use our functions to build a real (c) (hehe)
|
||||
-- database of car and people!
|
||||
testCarsPeopleDB :: CarsPeopleDB
|
||||
testCarsPeopleDB = let (res, db) = runState buildDB (CarsPeopleDB [] [])
|
||||
in db
|
||||
where buildDB = do
|
||||
addPerson "John"
|
||||
addPerson "Mary"
|
||||
addCar "Mercedes"
|
||||
addCar "Renault"
|
||||
|
||||
johnKnown <- personKnown "John"
|
||||
if not johnKnown
|
||||
then addPerson "Bruce"
|
||||
else return () -- We have to do something in the else
|
||||
-- branch as well, so let's just pack an
|
||||
-- empty tuple.
|
||||
|
||||
|
||||
|
||||
{- In this section we will define a simplified version of the Reader
|
||||
monad.-}
|
||||
|
||||
-- | The Reader monad is essentially the read-only State monad. Since
|
||||
-- the state never changes, we do not need to deal with functions of
|
||||
-- the form 's -> (a,s)', but only 's -> a' (there is no new state, it
|
||||
-- is always the same).
|
||||
--
|
||||
-- We will use the so-called "record syntax" to define the accessor
|
||||
-- for the single field of MyReader. It is really a syntactic sugar
|
||||
-- for
|
||||
--
|
||||
-- data MyReader s a = MyReader (s -> a)
|
||||
-- runReader :: MyReader s a -> (s -> a)
|
||||
-- runReader (MyReader f) = f
|
||||
data MyReader s a = MyReader { runReader :: s -> a
|
||||
}
|
||||
|
||||
-- | The 'Monad' instance for 'MyReader' is defined in _exactly_ the
|
||||
-- same way as for 'MyState'.
|
||||
instance Monad (MyReader s) where
|
||||
return x = MyReader (\s -> x)
|
||||
|
||||
-- | This time we don't have to deal with the new state, so things
|
||||
-- are simpler. We first run 'x' with the state 's'; it returns an
|
||||
-- instance of 'a'. Then we feed this result to 'f', and then run
|
||||
-- the resulting reader with the same state 's'.
|
||||
x >>= f = MyReader (\s -> runReader (f (runReader x s)) s )
|
||||
|
||||
-- | Again, some copy and paste.
|
||||
instance Applicative (MyReader s) where
|
||||
pure = return
|
||||
u <*> v = do
|
||||
f <- u
|
||||
x <- v
|
||||
return (f x)
|
||||
|
||||
instance Functor (MyReader s) where
|
||||
fmap f x = pure f <*> x
|
||||
|
||||
-- | And a small function to tell us our read-only state. (It's
|
||||
-- exactly like 'myGet', but is called differently).
|
||||
myAsk :: MyReader s s
|
||||
myAsk = MyReader (\s -> s)
|
||||
-- myAsk = MyReader id
|
||||
|
||||
{- And here's a very simple and stupid example. We will write
|
||||
functions generating various greetings for a name, which will be
|
||||
obtained from the reader monad-}
|
||||
|
||||
hello :: MyReader Person String
|
||||
hello = do
|
||||
name <- myAsk
|
||||
return $ "Hello " ++ name ++ "!" -- The $ is just a shortcut for not
|
||||
-- writing parentheses.
|
||||
-- return ("Hello " ++ name ++ "!")
|
||||
|
||||
howdy :: MyReader Person String
|
||||
howdy = do
|
||||
name <- myAsk
|
||||
return $ "Howdy " ++ name ++ "!"
|
||||
|
||||
goodMorning :: MyReader Person String
|
||||
goodMorning = do
|
||||
name <- myAsk
|
||||
return $ "Good morning " ++ name ++ "!"
|
||||
|
||||
-- This function produces all possible (hehe) greetings for the given
|
||||
-- name.
|
||||
--
|
||||
-- Mind the pointfree style! The function may actually be written as
|
||||
--
|
||||
-- greetings name = runReader mkGreetings name
|
||||
--
|
||||
greetings :: String -> [String]
|
||||
greetings = runReader mkGreetings
|
||||
where mkGreetings = do
|
||||
hl <- hello
|
||||
hw <- howdy
|
||||
gm <- goodMorning
|
||||
return [hl,hw,gm]
|
||||
|
||||
|
||||
|
||||
{- In this section we will define a simplified version of the Writer
|
||||
monad.-}
|
||||
|
||||
-- | The Writer monad is essentially a write-only State monad. Since
|
||||
-- the functions in this monad do not need to access the state, the
|
||||
-- type of the Writer monad is really really simple: just a tuple.
|
||||
--
|
||||
-- (Mind the record syntax.)
|
||||
data MyWriter s a = MyWriter { runWriter :: (a,s) }
|
||||
|
||||
-- | There's a catch however: we're not really interested in a
|
||||
-- write-only state: this would mean that we always get the state set
|
||||
-- by the last function. That is why in the Writer monad the state is
|
||||
-- actually an accumulator and, instead of changing it directly, the
|
||||
-- functions in this monad append stuff to it. Therefore, the Writer
|
||||
-- monad is the logger monad.
|
||||
--
|
||||
-- What we have just said means that we require some special
|
||||
-- properties of 's' (some special structure): we should be able to
|
||||
-- put together (append, concatenate) to values of type 's', like
|
||||
-- lists or with strings. This property is described by the typeclass
|
||||
-- 'Monoid'; types belonging to this typeclass have the function
|
||||
-- 'mappend' (or '<>', which is the same thing) which can append two
|
||||
-- values, and also the function 'mempty', returning the "empty"
|
||||
-- element (like the empty list or the empty string).
|
||||
instance (Monoid s) => Monad (MyWriter s) where
|
||||
-- | This where the 'mempty' function for the type 's' comes in handy:
|
||||
-- return "packs" the value "x" together with an empty log.
|
||||
return x = MyWriter (x,mempty)
|
||||
|
||||
-- | We have to take the result out of 'x' and feed it into 'f', and
|
||||
-- then put together the logs of the two.
|
||||
x >>= f = let (r, w ) = runWriter $ x
|
||||
(r',w') = runWriter $ f r
|
||||
in MyWriter (r', w <> w')
|
||||
|
||||
-- | Some more instances...
|
||||
instance (Monoid s) => Applicative (MyWriter s) where
|
||||
pure = return
|
||||
u <*> v = do
|
||||
f <- u
|
||||
x <- v
|
||||
return $ f x
|
||||
|
||||
instance (Monoid s) => Functor (MyWriter s) where
|
||||
fmap f x = pure f <*> x
|
||||
|
||||
-- | ... and a small function appending something to the log.
|
||||
myTell :: Monoid s => s -> MyWriter s ()
|
||||
myTell x = MyWriter ((), x)
|
||||
|
||||
{- And now it's time for an eccentric example! Let's make a weirdly
|
||||
verbose calculator.-}
|
||||
|
||||
-- | The following functions carry out some operations, but also log
|
||||
-- what they do to the Writer monad.
|
||||
myAdd :: Double -> Double -> MyWriter [String] Double
|
||||
myAdd x y = do
|
||||
myTell $ [show x ++ "+" ++ show y]
|
||||
return $ x + y
|
||||
|
||||
mySub :: Double -> Double -> MyWriter [String] Double
|
||||
mySub x y = do
|
||||
myTell $ [show x ++ "-" ++ show y]
|
||||
return $ x - y
|
||||
|
||||
myMul :: Double -> Double -> MyWriter [String] Double
|
||||
myMul x y = do
|
||||
myTell $ [show x ++ "*" ++ show y]
|
||||
return $ x * y
|
||||
|
||||
myDiv :: Double -> Double -> MyWriter [String] Double
|
||||
myDiv x y = do
|
||||
myTell $ [show x ++ "/" ++ show y]
|
||||
return $ x / y
|
||||
|
||||
-- | Let's test our calculator.
|
||||
testCalc :: (Double, [String])
|
||||
testCalc = runWriter $ do
|
||||
let x = 2
|
||||
y <- myMul x 2
|
||||
z <- mySub y 3
|
||||
t <- myDiv y 10
|
||||
a <- myAdd z t
|
||||
return a
|
229
content/courses/h4life/h4life-scanner.hs
Normal file
229
content/courses/h4life/h4life-scanner.hs
Normal file
|
@ -0,0 +1,229 @@
|
|||
{- scanner.hs
|
||||
|
||||
A simple parallel (and slightly concurrent) port scanner.
|
||||
|
||||
Type:
|
||||
|
||||
scanner 1 10 '127.0.0.1[30-50]' 'www.google.com[80-1000]'
|
||||
|
||||
to scan the ports 30 through 50 on localhost and ports 80 through 1000
|
||||
on www.google.com. The value 1 specifies the time the scanner will
|
||||
wait before dropping a connection attempt. The value 10 specifies the
|
||||
number of threads in the thread pool.
|
||||
|
||||
Only TCP connections are attempted.
|
||||
|
||||
This is an practical example to be shown in the fourth class of the
|
||||
course "Haskell for Life", by Sergiu Ivanov (sivanov@lacl.fr):
|
||||
|
||||
http://lacl.fr/~sivanov/doku.php?id=en:haskell_for_life
|
||||
|
||||
This file is distributed under the Creative Commons Attribution Alone
|
||||
licence.-}
|
||||
|
||||
import Network.Simple.TCP
|
||||
import Control.Monad.Catch
|
||||
import System.Timeout
|
||||
import Control.Concurrent.STM.TChan
|
||||
import Control.Concurrent.STM.TMVar
|
||||
import GHC.Conc.Sync
|
||||
import System.Environment (getArgs)
|
||||
|
||||
type Timeout = Int
|
||||
|
||||
-- | The possible outcomes of a connection attempt.
|
||||
data Outcome = Connected | Refused | TimedOut
|
||||
deriving Show
|
||||
|
||||
type PortNumber = Int
|
||||
|
||||
data ScanTarget = ScanTarget HostName PortNumber
|
||||
|
||||
-- | Attempts connecting to a port (service) on the given machine.
|
||||
--
|
||||
-- The timeout should be given in seconds.
|
||||
testPort :: HostName -> PortNumber -> Timeout -> IO Outcome
|
||||
testPort host port t =
|
||||
-- When the connection is actually refused, an IO error is thrown,
|
||||
-- and we use 'catchIOError' to handle that case. In the handling
|
||||
-- routine (the lambda function) we will not care about the type of
|
||||
-- the error, and just conclude that connection was refused.
|
||||
catchIOError (timedConnect host port t) (\_ -> return Refused)
|
||||
where
|
||||
-- 'connect' calls the supplied function if connection is
|
||||
-- successful (it's a lambda expression in our case). 'connect'
|
||||
-- actually gives the information about the connection (the
|
||||
-- socket), but we don't really care: once the lambda is called,
|
||||
-- the connection succeeded and the port is open.
|
||||
justConnect host port = connect host (show port) (\_ -> return Connected)
|
||||
|
||||
-- Aborts the attempts to connect after a given timeout.
|
||||
--
|
||||
-- 'timeout' returns 'Just result' if the function managed to get
|
||||
-- the result, and 'Nothing otherwise'. We know that our result
|
||||
-- can only be 'Connected', so no fancy pattern matching. (We may
|
||||
-- also get an IO error, but that is handled above.)
|
||||
timedConnect host port t = do
|
||||
let t' = 1000000 * t -- 'timeout' uses microseconds.
|
||||
|
||||
maybeResult <- timeout t' $ justConnect host port
|
||||
case maybeResult of
|
||||
Just Connected -> return Connected
|
||||
Nothing -> return TimedOut
|
||||
|
||||
-- | Runs a thread pool of the given size on the given channel
|
||||
-- containing tasks; waits until the tasks are done.
|
||||
--
|
||||
-- All the threads in the pool read tasks from the same channel and
|
||||
-- terminate when there are no more tasks in the channel. This means
|
||||
-- that the channel must contain data from the very beginning,
|
||||
-- otherwise the thread pool will just stop.
|
||||
threadPool :: Int -> TChan a -> (a -> IO ()) -> IO ()
|
||||
threadPool size chan process = do
|
||||
-- 'mapM' is like 'map', but it can handle functions returning IO
|
||||
-- values.
|
||||
--
|
||||
-- Here, the type of 'mapM' specialises to
|
||||
--
|
||||
-- mapM_ :: (a -> IO b) -> [a] -> IO [b]
|
||||
--
|
||||
-- So we do something for all numbers from 1 to 'size' (see the end
|
||||
-- of this expression). What we are going to do is spawn a worker
|
||||
-- thread and create a Boolean 'TMVar' into which the worker will
|
||||
-- put 'True' when it sees that the channel is empty. When all
|
||||
-- threads will have put 'True' into their corresponding variables,
|
||||
-- we can stop waiting.
|
||||
tmvars <- mapM (\_ -> do -- This argument gives us the number of the
|
||||
-- worker we are creating, but, hey, who cares!
|
||||
|
||||
-- Here's our flag for the worker to raise.
|
||||
tmvar <- atomically $ newEmptyTMVar
|
||||
|
||||
-- Spawn the worker now.
|
||||
forkIO $ worker chan tmvar
|
||||
|
||||
-- Store the flag.
|
||||
return tmvar
|
||||
) [1..size]
|
||||
|
||||
-- Now, the workers are working. We are going to wait on the
|
||||
-- corresponding flags until they are finished.
|
||||
--
|
||||
-- 'mapM_' is like 'mapM', but it discards the return values. We
|
||||
-- use it when we are only interested in the side effect: in this
|
||||
-- case, the waiting ('readTVar' blocks until the variable is not
|
||||
-- empty).
|
||||
mapM_ (\tmvar -> atomically $ takeTMVar tmvar) tmvars
|
||||
|
||||
where worker chan finished = do
|
||||
-- The code is pretty self-explanatory: try reading the
|
||||
-- channel atomically.
|
||||
maybeTask <- atomically $ tryReadTChan chan
|
||||
case maybeTask of
|
||||
-- There's still work to do; so process it and try getting
|
||||
-- some.
|
||||
Just task -> do
|
||||
process task
|
||||
-- This is a tail recursive call: it's the very last
|
||||
-- thing that happens in this function, so it actually
|
||||
-- works pretty much like a while and not like an
|
||||
-- infinite recursion.
|
||||
worker chan finished
|
||||
-- No more work, so this thread may stop ...
|
||||
Nothing -> do
|
||||
-- ... but tell the manager we're done before that.
|
||||
atomically $ putTMVar finished True
|
||||
|
||||
-- | Test 'theadPool'.
|
||||
--
|
||||
-- Load this file in GHCi and type 'testThreadPool' to see what
|
||||
-- happens :-)
|
||||
testThreadPool :: IO ()
|
||||
testThreadPool = do
|
||||
-- Will result in ["string1", "string2", ... , "string20"]
|
||||
--
|
||||
-- We use '(.)' to apply a series of transformations. First, we
|
||||
-- transform the number into a string. Second, we prepend "string"
|
||||
-- to this string. Remember, ("string" ++ ) is a function
|
||||
-- prepending the word "string" to whatever we give it. Thus
|
||||
-- ("string" ++) "1" == "string1".
|
||||
let strings = map (("string" ++) . show) [1..20]
|
||||
|
||||
-- Create a channel.
|
||||
chan <- newTChanIO
|
||||
|
||||
-- Put all the strings in the channel.
|
||||
atomically $ do
|
||||
-- It's all about partially applied functions again. 'writeTChan'
|
||||
-- is a function taking two arguments: the channel and the value
|
||||
-- to write. 'writeTChan chan' is a function taking _one_
|
||||
-- argument: the value to write to 'chan'.
|
||||
--
|
||||
-- We could also write:
|
||||
--
|
||||
-- mapM_ (\v -> writeTChan chan v) strings
|
||||
--
|
||||
mapM_ (writeTChan chan) strings
|
||||
|
||||
-- Create a thread pool of 3 threads, each calling 'putStrLn' on
|
||||
-- every string from 'chan.
|
||||
threadPool 3 chan putStrLn
|
||||
|
||||
-- Make sure we actually wait for the threads to finish.
|
||||
putStrLn "All threads finished!"
|
||||
|
||||
-- | Parses a string of the form "address[port1-port2]".
|
||||
--
|
||||
-- 'break' looks up the first symbol in the string for which the given
|
||||
-- function returns true, and returns the two halves of the list.
|
||||
--
|
||||
-- break odd [2,4,3,5] == ([2,4],[3,5])
|
||||
--
|
||||
parseAddress :: String -> (HostName, PortNumber, PortNumber)
|
||||
parseAddress str = let (host, sPorts) = break (== '[') str
|
||||
-- sPorts has the form "[number-number]". Drop
|
||||
-- the brackets.
|
||||
portsTrimmed = tail $ init sPorts
|
||||
-- Split on '-', and, in the second half, drop
|
||||
-- the dash immediately.
|
||||
(sPort1,(_:sPort2)) = break (== '-') portsTrimmed
|
||||
in (host, read sPort1, read sPort2)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
-- We will now suppose that the list of arguments has the right
|
||||
-- form. If something goes wrong, we are screwed :-)
|
||||
|
||||
-- This lets us get the first two elements of the list.
|
||||
(sTimeout:sNthreads:addresses) <- getArgs
|
||||
|
||||
let timeout = read sTimeout :: Int
|
||||
nthreads = read sNthreads :: Int
|
||||
|
||||
-- Create our channel for tasks.
|
||||
chan <- newTChanIO
|
||||
|
||||
-- We will parse the addresses one by one, generate the
|
||||
-- corresponding targets, and put them into the channel.
|
||||
mapM_ (\addr -> do
|
||||
-- Parse this address.
|
||||
let (host, port1, port2) = parseAddress addr
|
||||
|
||||
-- Now, iterate through the ports in the range
|
||||
-- 'port1'--'port2' and put the task containing the host
|
||||
-- name and the port into the channel.
|
||||
mapM_ (\port ->
|
||||
atomically $ writeTChan chan (ScanTarget host port)
|
||||
) [port1..port2]
|
||||
) addresses
|
||||
|
||||
-- Go ahead and scan all our targets in the thread pool of the given
|
||||
-- size.
|
||||
threadPool nthreads chan $
|
||||
(\(ScanTarget host port) -> do
|
||||
-- So, let's scan this target with the given timeout.
|
||||
outcome <- testPort host port timeout
|
||||
|
||||
-- Now show something.
|
||||
putStrLn $ host ++ ":" ++ show port ++ " -- " ++ show outcome
|
||||
)
|
169
content/courses/h4life/h4life-sorting.hs
Normal file
169
content/courses/h4life/h4life-sorting.hs
Normal file
|
@ -0,0 +1,169 @@
|
|||
{- sorting.hs
|
||||
|
||||
This file contains the examples on monads to be shown in the fourth
|
||||
class of the course "Haskell for Life", by Sergiu Ivanov
|
||||
(sivanov@lacl.fr):
|
||||
|
||||
http://lacl.fr/~sivanov/doku.php?id=en:haskell_for_life
|
||||
|
||||
This file is meant to be read sequentially.
|
||||
|
||||
This file is distributed under the Creative Commons Attribution Alone
|
||||
licence.-}
|
||||
|
||||
import Control.Parallel (par, pseq)
|
||||
import Control.DeepSeq (force, NFData, deepseq)
|
||||
import System.Random (getStdGen, randoms)
|
||||
import System.Environment (getArgs)
|
||||
import System.Clock
|
||||
|
||||
-- | This is an implementation of Quicksort.
|
||||
--
|
||||
-- We always choose the head of the list as the pivot, then we put the
|
||||
-- elements smaller than the pivot in 'lesser', and those greater than
|
||||
-- the pivot in 'greater'. We then sort the two lists and put them
|
||||
-- together with the pivot in between.
|
||||
--
|
||||
-- The 'Ord' typeclass requires comparison operators (like >=) to be
|
||||
-- defined for the type.
|
||||
qsort :: Ord a => [a] -> [a]
|
||||
qsort [] = []
|
||||
qsort (pivot:rest) = lesser ++ pivot:greater
|
||||
where lesser = qsort [x | x <- rest, x < pivot]
|
||||
greater = qsort [y | y <- rest, y >= pivot]
|
||||
|
||||
|
||||
-- | This is a parallel implementation of Quicksort.
|
||||
--
|
||||
-- We would like to have 'lesser' and 'greater' evaluated in parallel.
|
||||
-- To assure that, we may feel tempted to say
|
||||
--
|
||||
-- lesser `par` (lesser ++ pivot:greater)
|
||||
--
|
||||
-- In this case the runtime will get the information that we would
|
||||
-- like 'lesser' evaluated in parallel, ... and then the main thread
|
||||
-- will start evaluating the arguments to '(++)', sometimes starting
|
||||
-- with the very same 'lesser'! This means parallel evaluation of
|
||||
-- 'lesser' will only be initiated if we get lucky and the runtime
|
||||
-- starts evaluating the second argument of '(++)' before the first
|
||||
-- one. In order to give 'lesser' some time to get evaluated, we
|
||||
-- shall therefore also require that 'greater' be evaluated before
|
||||
-- list concatenation is done, like this:
|
||||
--
|
||||
-- lesser `par` ( greater `pseq` (lesser ++ pivot:greater) )
|
||||
--
|
||||
-- Moreover, since both `par` and `pseq` are right-associative, we can
|
||||
-- drop some of the parentheses:
|
||||
--
|
||||
-- lesser `par` greater `pseq` (lesser ++ pivot:greater)
|
||||
--
|
||||
-- Now, even though everything seems nice and shiny, there's still a
|
||||
-- big problem: 'par' and 'pseq' only assure some very "shallow"
|
||||
-- evaluation of their left argument. In the case of our sorting
|
||||
-- routine this essentially means that only the first elements of
|
||||
-- 'lesser' and 'greater' are actually evaluated, in parallel, while
|
||||
-- the rest of the lists are evaluated when the sorted result is
|
||||
-- _actually_ needed (e.g. when we want to print it); and that
|
||||
-- evaluation is done sequentially. Therefore, despite our clever
|
||||
-- usage of 'par' and 'pseq', the greater part of the sorting is done
|
||||
-- quite sequentially.
|
||||
--
|
||||
-- To solve this problem, we should force deep (complete) evaluation
|
||||
-- of 'lesser' and 'greater' using the aptly named 'force' function:
|
||||
--
|
||||
-- force lesser `par` force greater `pseq` (lesser ++ pivot:greater)
|
||||
--
|
||||
-- In this case we will really get our sorting executed in parallel.
|
||||
--
|
||||
-- We need to use the function 'force' on lists of type 'a', and thus
|
||||
-- on values of type 'a' as well. The existence of the function
|
||||
-- 'force' is expressed by the typeclass 'NFData'.
|
||||
parQsort :: (Ord a, NFData a) => [a] -> [a]
|
||||
parQsort [] = []
|
||||
parQsort (pivot:rest) = force lesser
|
||||
`par` force greater
|
||||
`pseq` (lesser ++ pivot:greater)
|
||||
where lesser = parQsort [x | x <- rest, x < pivot]
|
||||
greater = parQsort [y | y <- rest, y >= pivot]
|
||||
|
||||
|
||||
-- | Now, just for fun, let's consider the number of times we use
|
||||
-- 'par': we apply it in every node of the recursion tree in
|
||||
-- 'parQsort', and there more nodes in this recursion tree than there
|
||||
-- elements in the array to sort! That's actually a problem, since
|
||||
-- 'par' is not exactly free, even though it's not really costly
|
||||
-- either.
|
||||
--
|
||||
-- To avoid allocating separate jobs for very small lists, we will
|
||||
-- only use `par` for sorting lists longer than a certain threshold.
|
||||
--
|
||||
-- The symbol '@' allows us to refer to the deconstructed list. Thus,
|
||||
-- when we pattern match on an argument using 'list@(x:xs)' instead of
|
||||
-- just '(x:xs)', we can use 'list' to refer to the original (not
|
||||
-- deconstructed) value of the parameter (i.e. to 'list = (x:xs)').
|
||||
parnQsort :: (Ord a, NFData a) => Int -> [a] -> [a]
|
||||
parnQsort _ [] = []
|
||||
parnQsort d list@(pivot:rest) | length list >= d = lesser
|
||||
`par` greater
|
||||
`pseq` (lesser ++ pivot:greater)
|
||||
| otherwise = lesser ++ pivot:greater
|
||||
where lesser = parnQsort d [x | x <- rest, x < pivot]
|
||||
greater = parnQsort d [y | y <- rest, y >= pivot]
|
||||
|
||||
|
||||
-- | Produces a sequence of random integers of the given length.
|
||||
--
|
||||
-- Pseudo-random numbers are usually generating from some information
|
||||
-- from the outer world, that is why we have to work within the IO
|
||||
-- monad.
|
||||
randomInts :: Int -> IO [Int]
|
||||
randomInts n = do
|
||||
gen <- getStdGen -- Get the global random number generator.
|
||||
let ints = randoms gen -- Get an infinite list of random 'Int's.
|
||||
return $ take n ints -- Return the first 'n' random values.
|
||||
|
||||
-- | Runs an IO action and then outputs the running time.
|
||||
--
|
||||
-- Let's look at the type signature: according to its return type,
|
||||
-- this function is in the IO monad, and it returns a 'TimeSpec', a
|
||||
-- type defined in 'Data.Clock' that contains two fields: one for
|
||||
-- seconds and another one for nanoseconds. The only argument of this
|
||||
-- function is an _unevaluated_ value (a thunk) which the function
|
||||
-- will evaluate and measure the time it took. The evaluation will be
|
||||
-- forced via the 'deepseq' function, and the existence of this
|
||||
-- function for the type 'a' is guaranteed by requiring that it should
|
||||
-- belong to the 'NFData' typeclass.
|
||||
clockedRun :: NFData a => a -> IO TimeSpec
|
||||
clockedRun x = do
|
||||
-- We get the time from the monotonic clock: its value can never be
|
||||
-- changed, so we do not risk getting biased results.
|
||||
start <- getTime Monotonic
|
||||
end <- x `deepseq` getTime Monotonic
|
||||
return (diffTimeSpec start end)
|
||||
|
||||
-- | Let's test all three functions.
|
||||
main :: IO ()
|
||||
main = do
|
||||
args <- getArgs -- Get the command line arguments.
|
||||
|
||||
-- If there are some arguments, treat the first one as the length of
|
||||
-- the list; otherwise, use the default length: 100000 with many zeros.
|
||||
let n | null args = 100000
|
||||
| otherwise = read (head args)
|
||||
|
||||
-- Generate the random list.
|
||||
putStrLn $ "Generating " ++ show n ++ " random integers."
|
||||
list <- randomInts n
|
||||
|
||||
-- Run the sequential Quicksort and measure the time.
|
||||
seqTime <- clockedRun (qsort list)
|
||||
putStrLn $ "Sequential Quicksort worked in " ++ show (nsec seqTime) ++ " ns."
|
||||
|
||||
-- Run the first version of parallel Quicksort and measure the time.
|
||||
parTime <- clockedRun (parQsort list)
|
||||
putStrLn $ "Parallel Quicksort (abusive variant) worked in " ++ show (nsec parTime) ++ " ns."
|
||||
|
||||
-- Run the cleverer version of parallel Quicksort and measure the
|
||||
-- time. Don't use 'par' for lists having less than 1000 elements.
|
||||
parnTime <- clockedRun (parnQsort 1000 list)
|
||||
putStrLn $ "Parallel Quicksort (cleverer variant) worked in " ++ show (nsec parnTime) ++ " ns."
|
40
content/courses/h4life/h4life-typeclasses.hs
Normal file
40
content/courses/h4life/h4life-typeclasses.hs
Normal file
|
@ -0,0 +1,40 @@
|
|||
{- typeclasses.hs
|
||||
|
||||
This file contains some of the examples on typeclasses to be shown in
|
||||
the second class of the course "Haskell for Life", by Sergiu Ivanov
|
||||
(sivanov@lacl.fr):
|
||||
|
||||
http://lacl.fr/~sivanov/doku.php?id=en:haskell_for_life
|
||||
|
||||
This file is distributed under the Creative Commons Attribution Alone
|
||||
licence.-}
|
||||
|
||||
-- | A simple redefinition of the 'Eq' typeclass.
|
||||
class BasicEq a where
|
||||
isEqual :: a -> a -> Bool
|
||||
|
||||
isElement :: BasicEq a => a -> [a] -> Bool
|
||||
isElement _ [] = False
|
||||
isElement y (x:xs) | isEqual y x = True
|
||||
| otherwise = isElement y xs
|
||||
|
||||
instance BasicEq Bool where
|
||||
isEqual True True = True
|
||||
isEqual False False = True
|
||||
isEqual _ _ = False
|
||||
|
||||
instance BasicEq a => BasicEq [a] where
|
||||
isEqual [] [] = True
|
||||
isEqual _ [] = False
|
||||
isEqual [] _ = False
|
||||
isEqual (x:xs) (y:ys) | isEqual x y = isEqual xs ys
|
||||
| otherwise = False
|
||||
|
||||
|
||||
-- | This typeclasses are simpler than 'Show' and 'Read', but
|
||||
-- essentially capture their principal function.
|
||||
class SimplifiedShow a where
|
||||
show :: a -> String
|
||||
|
||||
class SimplifiedRead a where
|
||||
read :: String -> a
|
61
content/courses/h4life/h4life-types.hs
Normal file
61
content/courses/h4life/h4life-types.hs
Normal file
|
@ -0,0 +1,61 @@
|
|||
{- types.hs
|
||||
|
||||
This file contains some of the examples on types to be shown in the
|
||||
second class of the course "Haskell for Life", by Sergiu Ivanov
|
||||
(sivanov@lacl.fr):
|
||||
|
||||
http://lacl.fr/~sivanov/doku.php?id=en:haskell_for_life
|
||||
|
||||
This file is distributed under the Creative Commons Attribution Alone
|
||||
licence.-}
|
||||
|
||||
data BookInfo = Book Int String [String]
|
||||
deriving (Show)
|
||||
|
||||
data Coordinates = Cartesian2D Double Double | Polar2D Double Double
|
||||
deriving (Show)
|
||||
|
||||
printCoordinate :: Coordinates -> String
|
||||
printCoordinate (Cartesian2D x y) = "x=" ++ show x ++ ",y=" ++ show y
|
||||
printCoordinate (Polar2D ρ φ) = "ρ=" ++ show ρ ++ ",φ=" ++ show φ
|
||||
|
||||
data Pair a = Pair a a
|
||||
deriving (Show)
|
||||
|
||||
|
||||
-- | Returns 'True' if the given 'Maybe' contains 'Nothing'.
|
||||
myIsNothing :: Maybe a -> Bool
|
||||
myIsNothing Nothing = True
|
||||
myIsNothing (Just _) = False
|
||||
|
||||
-- | Extracts the contents of a 'Maybe' (unpacks it).
|
||||
myFromJust :: Maybe a -> a
|
||||
myFromJust (Just x) = x
|
||||
|
||||
|
||||
data List a = Cons a (List a) | Nil
|
||||
deriving (Show)
|
||||
|
||||
-- | Converts an instance of 'List' to a normal Haskell list.
|
||||
fromMyList :: List a -> [a]
|
||||
fromMyList (Cons x xs) = x:fromMyList xs
|
||||
fromMyList Nil = []
|
||||
|
||||
-- | Converts a normal Haskell list to an instance of 'List'.
|
||||
toMyList :: [a] -> List a
|
||||
toMyList (x:xs) = Cons x (toMyList xs)
|
||||
toMyList [] = Nil
|
||||
|
||||
|
||||
data FunnyList a = a :/ (FunnyList a) | Void
|
||||
deriving Show
|
||||
|
||||
|
||||
data Tree a = Node a (Tree a) (Tree a) | Empty
|
||||
deriving (Show, Read)
|
||||
|
||||
-- | Returns a list of the values contained in the leaves of a tree.
|
||||
fruit :: Tree a -> [a]
|
||||
fruit Empty = []
|
||||
fruit (Node x Empty Empty) = [x]
|
||||
fruit (Node _ l r) = fruit l ++ fruit r
|
BIN
content/courses/h4life/marlow-tutorial.pdf
Normal file
BIN
content/courses/h4life/marlow-tutorial.pdf
Normal file
Binary file not shown.
104
en/h4life.org
Normal file
104
en/h4life.org
Normal file
|
@ -0,0 +1,104 @@
|
|||
#+TITLE: Haskell for Life
|
||||
|
||||
#+LANGUAGE: en
|
||||
|
||||
#+ATTR_HTML: :alt in French :class lang-lifted
|
||||
[[file:../fr/h4life.org][file:../content/imgs/fr.png]]
|
||||
|
||||
#+ATTR_HTML: :alt return home :class home
|
||||
[[file:home.org][file:../content/imgs/home.png]]
|
||||
|
||||
The goal of this short course is to familiarise the students with the
|
||||
computational model behind a mainstream high-abstraction functional
|
||||
programming language, with focus on developing practical skills. After
|
||||
having taken this course, the students will be able to solve practical
|
||||
programming problems of medium complexity by writing idiomatic Haskell
|
||||
code and making use of the rich library ecosystem.
|
||||
|
||||
The course is intended for a wide audience and only requires basic
|
||||
programming skills in one of the other mainstream languages (C/C++,
|
||||
Java, C#/.NET, Python, etc.).
|
||||
|
||||
The course relies heavily on showing actual interactive Haskell
|
||||
sessions and should be directly driven by feedback from the students.
|
||||
|
||||
The course consists of 4 parts, the first 3 of which introduce some
|
||||
essential concepts, and the last one focuses on practical examples of
|
||||
parallel and concurrent programming. The parts of the course may be
|
||||
taught over multiple sessions of about 2 hours; in particular, the
|
||||
first part should most probably be spread over two sessions to help
|
||||
students digest the basic concepts. At the end of the course the
|
||||
students may be required to submit a course project. The students who
|
||||
submit a working course project may be awarded 2 ECTS.
|
||||
|
||||
The course is somewhat loosely based on the awesome book [[http://book.realworldhaskell.org/][Real World
|
||||
Haskell]].
|
||||
|
||||
#+ATTR_HTML: :alt image of Creative Commons Attribution Alone licence :class ccby
|
||||
[[https://en.wikipedia.org/wiki/Creative_Commons_license][file:../content/imgs/ccby.png]]
|
||||
|
||||
The materials of this course are distributed under the [[https://en.wikipedia.org/wiki/Creative_Commons_license][Creative
|
||||
Commons Attribution Alone licence]].
|
||||
|
||||
|
||||
* Introduction
|
||||
The fundamental ideas behind functional programming are introduced,
|
||||
with particular focus on handling functions as first-class values
|
||||
and on higher order functions. Strict and static typing discipline
|
||||
in Haskell is discussed and compared to typing disciplines in other
|
||||
mainstream programming languages. Basic syntax is described along
|
||||
with the explanations, including the syntax for definitions of
|
||||
simple data types.
|
||||
|
||||
The slides of this part are available [[file:../content/courses/h4life/h4life-01.pdf][here]]. [[file:../content/courses/h4life/h4life-funcs.hs][This file]] contains the
|
||||
examples.
|
||||
|
||||
* Typeclasses
|
||||
Typeclasses in Haskell are introduced and compared to classes and
|
||||
types in other mainstream programming languages. Subtle differences
|
||||
are pointed out and discussed at length to avoid confusion.
|
||||
|
||||
The slides of this part are available [[file:../content/courses/h4life/h4life-02.pdf][here]]. [[file:../content/courses/h4life/h4life-types.hs][This]] and [[file:../content/courses/h4life/h4life-typeclasses.hs][this file]]
|
||||
contain the examples. The former file contains Unicode characters,
|
||||
so be sure to choose the correct Unicode encoding (UTF-8) to view it
|
||||
properly.
|
||||
|
||||
* Monads
|
||||
Monads as a way to encapsulate repetitive details of the computation
|
||||
are introduced. The =Maybe= and =List= monads are described. An
|
||||
overview of the monads =Reader=, =Writer=, and =State= is provided.
|
||||
|
||||
The slides of this part are available [[file:../content/courses/h4life/h4life-03.pdf][here]]. [[file:../content/courses/h4life/h4life-monads.hs][This file]] contains the
|
||||
examples.
|
||||
|
||||
* Parallel and Concurrent Programming
|
||||
Techniques of parallel and concurrent programming in Haskell are
|
||||
described. Sparks as building blocks for a parallel program are
|
||||
discussed. Spawning threads and synchronising them via thread-safe
|
||||
shared storage is covered. A basic port scanner is presented as a
|
||||
practical example.
|
||||
|
||||
The slides of this part are available [[file:../content/courses/h4life/h4life-04.pdf][here]]. The sorting example can
|
||||
be found [[file:../content/courses/h4life/h4life-sorting.hs][here]]. The source code of the port scanner is available
|
||||
[[file:../content/courses/h4life/h4life-scanner.hs][here]].
|
||||
|
||||
For further details on parallel and concurrent programming in
|
||||
Haskell, check out [[file:../content/courses/h4life/marlow-tutorial.pdf][Simon Marlow's tutorial]].
|
||||
|
||||
* Teaching Sites
|
||||
I taught this course at the [[http://www.tucs.fi/][Turku Center for Computer Science]]
|
||||
(January 2016), at [[https://www.pentalog.com/locations/chisinau-nearshore-delivery-center/][Pentalog Chișinău]] (December 2016), and at the
|
||||
[[http://fcim.utm.md/][Faculty of Computer Science of the Technical University of Moldova]]
|
||||
(December 2016).
|
||||
|
||||
The exam subject for the version of the course I taught at the Turku
|
||||
Center of Computer Science can be found [[file:../content/courses/h4life/h4life-exam.pdf][here]].
|
||||
|
||||
|
||||
* Local Variables :noexport:
|
||||
# Local Variables:
|
||||
# org-link-file-path-type: relative
|
||||
# eval: (auto-fill-mode)
|
||||
# ispell-local-dictionary: "en"
|
||||
# End:
|
||||
|
|
@ -66,8 +66,8 @@ Jump to:
|
|||
- [[file:alife-intro.org][a very quick introduction to artificial life]]
|
||||
- [[file:pn-biomodelling.org][Petri nets for biomodelling]]
|
||||
- [[file:cytoscape-intro.org][introduction to Cytoscape]]
|
||||
- Haskell for Life
|
||||
- [[file:togit.org][To Git or Not to Git]]
|
||||
- [[file:h4life.org][Haskell for Life]]
|
||||
- operating systems and networks
|
||||
- basic parsing
|
||||
|
||||
|
|
116
fr/h4life.org
Normal file
116
fr/h4life.org
Normal file
|
@ -0,0 +1,116 @@
|
|||
#+TITLE: Haskell for Life
|
||||
|
||||
#+LANGUAGE: fr
|
||||
|
||||
#+ATTR_HTML: :alt en anglais :class lang-lifted
|
||||
[[file:../en/h4life.org][file:../content/imgs/en.png]]
|
||||
|
||||
#+ATTR_HTML: :alt return home :class home
|
||||
[[file:home.org][file:../content/imgs/home.png]]
|
||||
|
||||
Le but de ce cours de courte durée est de familiariser les étudiants
|
||||
avec le modèle de calcul derrière un langage fonctionnel de haut
|
||||
niveau et grand public. Le cours se focalise plutôt sur l'acquisition
|
||||
de connaissances pratiques. Grâce à ce cours, les étudiants seront
|
||||
capables de résoudre des problèmes pratiques de programmation de
|
||||
complexité moyenne en écrivant du code Haskell idiomatique et en
|
||||
utilisant l'écosystème riche de librairies propre à ce langage.
|
||||
|
||||
Ce cours vise un public large, le seul prérequis étant une expérience
|
||||
de programmation superficielle dans un des langages populaires :
|
||||
(C/C++, Java, C#/.NET, Python, etc.).
|
||||
|
||||
L'activité essentielle pour l'enseignement de ce cours est la
|
||||
démonstration de sessions interactives de Haskell. Le processus
|
||||
d'enseignement sera donc directement dirigé par le retour de la part
|
||||
des étudiants.
|
||||
|
||||
Le cours consiste en 4 parties, dont les 3 premières définissent
|
||||
quelques notions essentielles, alors que la dernière fournit des
|
||||
exemples de programmation parallèle et concurrente. Une partie peut
|
||||
être enseignée sur plusieurs séances de 2 heures ; c'est notamment le
|
||||
cas de la première partie qui devrait probablement être traitée en
|
||||
deux temps pour faciliter l'apprentissage. À la fin du cours les
|
||||
étudiants peuvent soumettre un projet ; les étudiants ayant réussi à
|
||||
soumettre un projet peuvent se voir accorder 2 ECTS.
|
||||
|
||||
Les dispositifs de ce cours ainsi que les explications dans le code
|
||||
source sont en anglais.
|
||||
|
||||
Ce cours suit approximativement le merveilleux livre [[http://book.realworldhaskell.org/][Real World
|
||||
Haskell]].
|
||||
|
||||
#+ATTR_HTML: :alt image de la licence Creative Commons Attribution Alone :class ccby
|
||||
[[https://fr.wikipedia.org/wiki/Licence_Creative_Commons][file:../content/imgs/ccby.png]]
|
||||
|
||||
Les matériaux de ce cours sont distribués sous la [[https://fr.wikipedia.org/wiki/Licence_Creative_Commons][licence Creative
|
||||
Commons Paternité]].
|
||||
|
||||
|
||||
* Introduction
|
||||
Cette partie introduit les idées fondamentales derrière la
|
||||
programmation fonctionnelle ; il s'agit des fonctions de première
|
||||
classe et des fonctions d'ordre supérieur. Le typage strict et
|
||||
statique en Haskell est présenté et comparé aux autres stratégies de
|
||||
typage que l'on retrouve dans d'autres langages de
|
||||
programmation. Quelques éléments essentiels de la syntaxe sont
|
||||
expliqués, y compris la syntaxe pour la définition de types de
|
||||
données simples.
|
||||
|
||||
Les diapositives de cette partie se trouvent [[file:../content/courses/h4life/h4life-01.pdf][ici]]. Les exemples
|
||||
présentés sont [[file:../content/courses/h4life/h4life-funcs.hs][ici]].
|
||||
|
||||
* Classes de types
|
||||
Cette partie définit les classes de types en Haskell et les compare
|
||||
aux classes et aux types dans d'autres langages de
|
||||
programmation. Quelques différences subtiles sont mises en avant
|
||||
pour éviter toute confusion.
|
||||
|
||||
Les diapositives de cette partie se trouvent [[file:../content/courses/h4life/h4life-02.pdf][ici]]. Les exemples
|
||||
présentés se trouvent [[file:../content/courses/h4life/h4life-types.hs][ici]] et encore [[file:../content/courses/h4life/h4life-typeclasses.hs][ici]]. Le premier de ces fichiers
|
||||
contient des caractères Unicode ; il faut donc bien choisir
|
||||
l'encodage de l'affichage (Unicode ou UTF-8).
|
||||
|
||||
* Monades
|
||||
Dans cette partie les monades sont présentées comme des outils pour
|
||||
l'élimination des aspects répétitifs d'un calcul. Dans un premier
|
||||
temps, les monades =Maybe= et =List= sont décrites. Ensuite, un
|
||||
aperçu des monades =Reader=, =Writer=, et =State= est proposé.
|
||||
|
||||
Les diapositives de cette partie se trouvent [[file:../content/courses/h4life/h4life-03.pdf][ici]]. Les exemples
|
||||
présentés se trouvent [[file:../content/courses/h4life/h4life-monads.hs][ici]].
|
||||
|
||||
* Parallel and Concurrent Programming
|
||||
Cette partie décrit quelques techniques de base pour la
|
||||
programmation parallèle et concurrente en Haskell. Dans un premier
|
||||
temps, les annotations pour la programmation parallèle sont
|
||||
présentées. Ensuite, une façon de lancer des fils d'exécution
|
||||
parallèle est décrite, ainsi que les stratégies de synchronisation
|
||||
par des variables partagées en exclusion mutuelle. Un scanner de
|
||||
ports rudimentaire est développé pour une démonstration pratique de
|
||||
ces techniques.
|
||||
|
||||
Les diapositives de cette partie se trouvent [[file:../content/courses/h4life/h4life-04.pdf][ici]]. L'implémentation
|
||||
du tri rapide présentée dans le cours est [[file:../content/courses/h4life/h4life-sorting.hs][ici]]. Le code du scanner de
|
||||
ports se trouve [[file:../content/courses/h4life/h4life-scanner.hs][ici]].
|
||||
|
||||
[[file:../content/courses/h4life/marlow-tutorial.pdf][Ce tutoriel]] (en anglais) sur la programmation parallèle et
|
||||
concurrente en Haskell par Simon Marlow donne (beaucoup) plus de
|
||||
détails.
|
||||
|
||||
* Sites d'enseignements
|
||||
J'ai enseigné ce cours à [[http://www.tucs.fi/][Turku Center for Computer Science]] (janvier
|
||||
2016), à [[https://www.pentalog.com/locations/chisinau-nearshore-delivery-center/][Pentalog Chișinău]] (décembre 2016), ainsi qu'à la [[http://fcim.utm.md/][faculté
|
||||
d'informatique de l'Université technique de Moldavie]] (décembre
|
||||
2016).
|
||||
|
||||
La consigne de l'examen pour la version du cours que j'ai enseignée
|
||||
à Turku Center for Computer Science se trouve [[file:../content/courses/h4life/h4life-exam.pdf][ici]].
|
||||
|
||||
|
||||
* Local Variables :noexport:
|
||||
# Local Variables:
|
||||
# org-link-file-path-type: relative
|
||||
# eval: (auto-fill-mode)
|
||||
# ispell-local-dictionary: "fr"
|
||||
# End:
|
|
@ -70,7 +70,7 @@ Liens rapides :
|
|||
- [[file:pn-biomodelling.org][réseaux de Petri pour la biomodélisation]]
|
||||
- [[file:cytoscape-intro.org][introduction à Cytoscape]]
|
||||
- [[file:togit.org][To Git or Not to Git]]
|
||||
- Haskell for Life
|
||||
- [[file:h4life.org][Haskell for Life]]
|
||||
- introduction au systèmes d'exploitation et réseaux
|
||||
- bases de l'analyse syntaxique
|
||||
|
||||
|
|
Loading…
Reference in a new issue