diff --git a/content/courses/h4life/h4life-01.pdf b/content/courses/h4life/h4life-01.pdf new file mode 100644 index 0000000..1efa351 Binary files /dev/null and b/content/courses/h4life/h4life-01.pdf differ diff --git a/content/courses/h4life/h4life-02.pdf b/content/courses/h4life/h4life-02.pdf new file mode 100644 index 0000000..1ada154 Binary files /dev/null and b/content/courses/h4life/h4life-02.pdf differ diff --git a/content/courses/h4life/h4life-03.pdf b/content/courses/h4life/h4life-03.pdf new file mode 100644 index 0000000..e59af8e Binary files /dev/null and b/content/courses/h4life/h4life-03.pdf differ diff --git a/content/courses/h4life/h4life-04.pdf b/content/courses/h4life/h4life-04.pdf new file mode 100644 index 0000000..72c508d Binary files /dev/null and b/content/courses/h4life/h4life-04.pdf differ diff --git a/content/courses/h4life/h4life-exam.pdf b/content/courses/h4life/h4life-exam.pdf new file mode 100644 index 0000000..02eafcc Binary files /dev/null and b/content/courses/h4life/h4life-exam.pdf differ diff --git a/content/courses/h4life/h4life-funcs.hs b/content/courses/h4life/h4life-funcs.hs new file mode 100644 index 0000000..de8dc34 --- /dev/null +++ b/content/courses/h4life/h4life-funcs.hs @@ -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) diff --git a/content/courses/h4life/h4life-monads.hs b/content/courses/h4life/h4life-monads.hs new file mode 100644 index 0000000..5575249 --- /dev/null +++ b/content/courses/h4life/h4life-monads.hs @@ -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 '). 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 diff --git a/content/courses/h4life/h4life-scanner.hs b/content/courses/h4life/h4life-scanner.hs new file mode 100644 index 0000000..e3f75ac --- /dev/null +++ b/content/courses/h4life/h4life-scanner.hs @@ -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 + ) diff --git a/content/courses/h4life/h4life-sorting.hs b/content/courses/h4life/h4life-sorting.hs new file mode 100644 index 0000000..f13cebb --- /dev/null +++ b/content/courses/h4life/h4life-sorting.hs @@ -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." diff --git a/content/courses/h4life/h4life-typeclasses.hs b/content/courses/h4life/h4life-typeclasses.hs new file mode 100644 index 0000000..d224b5b --- /dev/null +++ b/content/courses/h4life/h4life-typeclasses.hs @@ -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 diff --git a/content/courses/h4life/h4life-types.hs b/content/courses/h4life/h4life-types.hs new file mode 100644 index 0000000..12c57dc --- /dev/null +++ b/content/courses/h4life/h4life-types.hs @@ -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 diff --git a/content/courses/h4life/marlow-tutorial.pdf b/content/courses/h4life/marlow-tutorial.pdf new file mode 100644 index 0000000..badbef7 Binary files /dev/null and b/content/courses/h4life/marlow-tutorial.pdf differ diff --git a/en/h4life.org b/en/h4life.org new file mode 100644 index 0000000..ee2624e --- /dev/null +++ b/en/h4life.org @@ -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: + diff --git a/en/home.org b/en/home.org index 613ca21..b8318f8 100644 --- a/en/home.org +++ b/en/home.org @@ -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 diff --git a/fr/h4life.org b/fr/h4life.org new file mode 100644 index 0000000..9ab00df --- /dev/null +++ b/fr/h4life.org @@ -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: diff --git a/fr/home.org b/fr/home.org index 626918c..d0a754b 100644 --- a/fr/home.org +++ b/fr/home.org @@ -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