From cc7ee0390783cb7ec8826bb8e8036aac0f1bac20 Mon Sep 17 00:00:00 2001 From: "Yann Esposito (Yogsototh)" Date: Wed, 25 Dec 2019 22:17:22 +0100 Subject: [PATCH] wip --- src/posts/0010-Haskell-Now/fib_lazy.hs | 4 + src/posts/0010-Haskell-Now/index.org | 287 +++++++++++++----- src/posts/0010-Haskell-Now/infinite_tree.hs | 32 ++ src/posts/0010-Haskell-Now/infinite_tree_2.hs | 45 +++ src/posts/0010-Haskell-Now/io_sum.hs | 7 + src/posts/0010-Haskell-Now/io_sum_safe.hs | 18 ++ src/posts/0010-Haskell-Now/shell.nix | 3 + 7 files changed, 324 insertions(+), 72 deletions(-) create mode 100644 src/posts/0010-Haskell-Now/fib_lazy.hs create mode 100644 src/posts/0010-Haskell-Now/infinite_tree.hs create mode 100644 src/posts/0010-Haskell-Now/infinite_tree_2.hs create mode 100644 src/posts/0010-Haskell-Now/io_sum.hs create mode 100644 src/posts/0010-Haskell-Now/io_sum_safe.hs diff --git a/src/posts/0010-Haskell-Now/fib_lazy.hs b/src/posts/0010-Haskell-Now/fib_lazy.hs new file mode 100644 index 0000000..c91e058 --- /dev/null +++ b/src/posts/0010-Haskell-Now/fib_lazy.hs @@ -0,0 +1,4 @@ +fib :: [Integer] +fib = 1:1:zipWith (+) fib (tail fib) + +main = traverse_ print (take 20 (drop 200 fib)) diff --git a/src/posts/0010-Haskell-Now/index.org b/src/posts/0010-Haskell-Now/index.org index 4fb2a1a..343cb49 100644 --- a/src/posts/0010-Haskell-Now/index.org +++ b/src/posts/0010-Haskell-Now/index.org @@ -190,6 +190,7 @@ The article contains five parts: haskellDeps = ps: with ps; [ base protolude + containers ]; ghc = haskellPackages.ghcWithPackages haskellDeps; @@ -203,6 +204,9 @@ The article contains five parts: pkgs.stdenv.mkDerivation { name = "env"; buildInputs = nixPackages; + shellHook = '' + export PS1="\n[hs:\033[1;32m\]\W\[\033[0m\]]> " + ''; } #+end_src @@ -1751,7 +1755,8 @@ Node 7 (Node 2 Empty (Node 4 Empty Empty)) (Node 8 Empty Empty) This is an informative but quite unpleasant representation of our tree. I've added the =containers= package in the =shell.nix= file, it is time to -use this library which contain an helper to show trees. +use this library which contain functions to show trees and list of trees +(forest) named =drawTree= and =drawForest=. #+BEGIN_SRC haskell :tangle pretty_tree.hs import Data.Tree (Tree,Forest(..)) @@ -1814,8 +1819,6 @@ Int binary tree: | `- 23 - - Note we could also use another type String binary tree: @@ -1829,11 +1832,8 @@ String binary tree: | `- "yog" - - As we can test equality and order trees, we can make tree of trees! - Binary tree of Char binary trees: Node 'f' Empty (Node 'o' Empty Empty) | @@ -1914,18 +1914,41 @@ function =take= which is equivalent to our =take'=. This code is mostly the same as the previous one. -#+BEGIN_SRC haskell - import Debug.Trace (trace) - import Data.List +#+begin_src haskell :tangle infinite_tree.hs :exports none + import Data.Tree (Tree,Forest(..)) + import qualified Data.Tree as Tree + +#+end_src + + +#+BEGIN_SRC haskell :tangle infinite_tree.hs data BinTree a = Empty - | Node a (BinTree a) (BinTree a) - deriving (Eq,Ord) + | Node a (BinTree a) (BinTree a) + deriving (Eq,Ord,Show) #+END_SRC + +#+begin_src haskell :tangle infinite_tree.hs :exports none + + -- | Function to transform our internal BinTree type to the + -- type of Tree declared in Data.Tree (from containers package) + -- so that the function Tree.drawForest can use + binTreeToForestString :: (Show a) => BinTree a -> Forest String + binTreeToForestString Empty = [] + binTreeToForestString (Node x left right) = + [Tree.Node (show x) ((binTreeToForestString left) ++ (binTreeToForestString right))] + + -- | Function that given a BinTree print a representation of it in the console + prettyPrintTree :: (Show a) => BinTree a -> IO () + prettyPrintTree = putStrLn . Tree.drawForest . binTreeToForestString + +#+end_src + + Suppose we don't mind having an ordered binary tree. Here is an infinite binary tree: -#+BEGIN_SRC haskell +#+BEGIN_SRC haskell :tangle infinite_tree.hs nullTree = Node 0 nullTree nullTree #+END_SRC @@ -1933,7 +1956,7 @@ A complete binary tree where each node is equal to 0. Now I will prove you can manipulate this object using the following function: -#+BEGIN_SRC haskell +#+BEGIN_SRC haskell :tangle infinite_tree.hs -- take all element of a BinTree -- up to some depth treeTakeDepth _ Empty = Empty @@ -1947,34 +1970,83 @@ function: See what occurs for this program: -#+BEGIN_SRC haskell - main = print $ treeTakeDepth 4 nullTree +{{{lnk(infinite_tree.hs)}}} +#+BEGIN_SRC haskell :tangle infinite_tree.hs + main = prettyPrintTree (treeTakeDepth 4 nullTree) #+END_SRC This code compiles, runs and stops giving the following result: #+BEGIN_EXAMPLE -< 0 -: |-- 0 -: | |-- 0 -: | | |-- 0 -: | | `-- 0 -: | `-- 0 -: | |-- 0 -: | `-- 0 -: `-- 0 -: |-- 0 -: | |-- 0 -: | `-- 0 -: `-- 0 -: |-- 0 -: `-- 0 +[hs:hsenv]> runghc infinite_tree.hs +0 +| ++- 0 +| | +| +- 0 +| | | +| | +- 0 +| | | +| | `- 0 +| | +| `- 0 +| | +| +- 0 +| | +| `- 0 +| +`- 0 + | + +- 0 + | | + | +- 0 + | | + | `- 0 + | + `- 0 + | + +- 0 + | + `- 0 + #+END_EXAMPLE Just to heat up your neurones a bit more, let's make a slightly more interesting tree: -#+BEGIN_SRC haskell + +#+begin_src haskell :tangle infinite_tree_2.hs :exports none + import Data.Tree (Tree,Forest(..)) + import qualified Data.Tree as Tree + + data BinTree a = Empty + | Node a (BinTree a) (BinTree a) + deriving (Eq,Ord,Show) + + -- | Function to transform our internal BinTree type to the + -- type of Tree declared in Data.Tree (from containers package) + -- so that the function Tree.drawForest can use + binTreeToForestString :: (Show a) => BinTree a -> Forest String + binTreeToForestString Empty = [] + binTreeToForestString (Node x left right) = + [Tree.Node (show x) ((binTreeToForestString left) ++ (binTreeToForestString right))] + + -- | Function that given a BinTree print a representation of it in the console + prettyPrintTree :: (Show a) => BinTree a -> IO () + prettyPrintTree = putStrLn . Tree.drawForest . binTreeToForestString + + -- | take all element of a BinTree up to some depth + treeTakeDepth _ Empty = Empty + treeTakeDepth 0 _ = Empty + treeTakeDepth n (Node x left right) = let + nl = treeTakeDepth (n-1) left + nr = treeTakeDepth (n-1) right + in + Node x nl nr +#+end_src + + +#+BEGIN_SRC haskell :tangle infinite_tree_2.hs iTree = Node 0 (dec iTree) (inc iTree) where dec (Node x l r) = Node (x-1) (dec l) (dec r) @@ -1986,7 +2058,7 @@ This function should be similar to =map=, but should work on =BinTree= instead of list. Here is such a function: -#+BEGIN_SRC haskell +#+BEGIN_SRC haskell :tangle infinite_tree_2.hs -- apply a function to each node of Tree treeMap :: (a -> b) -> BinTree a -> BinTree b treeMap f Empty = Empty @@ -2001,7 +2073,7 @@ structures, search for functor and =fmap=. Our definition is now: -#+BEGIN_SRC haskell +#+BEGIN_SRC haskell :tangle infinite_tree_2.hs infTreeTwo :: BinTree Int infTreeTwo = Node 0 (treeMap (\x -> x-1) infTreeTwo) (treeMap (\x -> x+1) infTreeTwo) @@ -2009,33 +2081,102 @@ Our definition is now: Look at the result for -#+BEGIN_SRC haskell - main = print $ treeTakeDepth 4 infTreeTwo +{{{lnk(infinite_tree_2.hs)}}} +#+BEGIN_SRC haskell :tangle infinite_tree_2.hs + main = prettyPrintTree $ treeTakeDepth 4 infTreeTwo #+END_SRC #+BEGIN_EXAMPLE -< 0 -: |-- -1 -: | |-- -2 -: | | |-- -3 -: | | `-- -1 -: | `-- 0 -: | |-- -1 -: | `-- 1 -: `-- 1 -: |-- 0 -: | |-- -1 -: | `-- 1 -: `-- 2 -: |-- 1 -: `-- 3 +[hs:hsenv]> runghc infinite_tree_2.hs +0 +| ++- -1 +| | +| +- -2 +| | | +| | +- -3 +| | | +| | `- -1 +| | +| `- 0 +| | +| +- -1 +| | +| `- 1 +| +`- 1 + | + +- 0 + | | + | +- -1 + | | + | `- 1 + | + `- 2 + | + +- 1 + | + `- 3 #+END_EXAMPLE -#+BEGIN_SRC haskell - main = do - print $ treeTakeDepth 4 nullTree - print $ treeTakeDepth 4 infTreeTwo -#+END_SRC +#+begin_notes +The important things to remember. +Haskell handle infinite structures naturally mostly because it is not strict. + +So you can write, infinite tree, but also, you can generate infinite list +like this common example: + +#+begin_src haskell :tangle fib_lazy.hs +fib :: [Integer] +fib = 1:1:zipWith (+) fib (tail fib) + +main = traverse_ print (take 20 (drop 200 fib)) +#+end_src + +Many new details in this small code. Don't worry if you do not get all details: + +- =fib= is a list of Integer, not a function +- =(!!)= is used to get the nth element of a list. +- =drop n= remove n element of a list +- =take n= keep the first n elements of a list +- =zipWith op [a1,a2,a3,...] [b1,b2,b3,...]= will generate the list + =[op a1 b1,op a2 b2,op a3 b3, .... ]= +- =traverse_= is like map but for performing effects (in this case print) + and discarding any result if any. + +This progam print all fibonnacci numbers from 201 to 221 instantaneously. +Because, =fib= is a list that will be used as "cache" to compute each +number even considering the code looks a bit like a double recursion. + +#+begin_example +[hs:0010-Haskell-Now]> time runghc fib_lazy.hs +453973694165307953197296969697410619233826 +734544867157818093234908902110449296423351 +1188518561323126046432205871807859915657177 +1923063428480944139667114773918309212080528 +3111581989804070186099320645726169127737705 +5034645418285014325766435419644478339818233 +8146227408089084511865756065370647467555938 +13180872826374098837632191485015125807374171 +21327100234463183349497947550385773274930109 +34507973060837282187130139035400899082304280 +55835073295300465536628086585786672357234389 +90343046356137747723758225621187571439538669 +146178119651438213260386312206974243796773058 +236521166007575960984144537828161815236311727 +382699285659014174244530850035136059033084785 +619220451666590135228675387863297874269396512 +1001919737325604309473206237898433933302481297 +1621140188992194444701881625761731807571877809 +2623059926317798754175087863660165740874359106 +4244200115309993198876969489421897548446236915 + +real 0m1.000s +user 0m0.192s +sys 0m0.058s +#+end_example + +#+end_notes * Difficulty: Hard :PROPERTIES: @@ -2043,7 +2184,7 @@ Look at the result for :END: Congratulations for getting so far! -Now, some of the really hardcore stuff can start. +Now, some of the really hard stuff can start. If you are like me, you should get the functional style. You should also understand a bit more the advantages of laziness by @@ -2067,9 +2208,9 @@ But they are all very rewarding. [[./magritte_carte_blanche.jpg]] #+BEGIN_QUOTE - tldr; +{{{tldr}}} - A typical function doing =IO= looks a lot like an imperative program: +A typical function doing =IO= looks a lot like an imperative program: #+BEGIN_SRC haskell f :: IO a @@ -2083,11 +2224,14 @@ But they are all very rewarding. - To set a value to an object we use =<-= . - The type of each line is =IO *=; in this example: - - =action1 :: IO b= - - =action2 x :: IO ()= - - =action3 :: IO c= - - =action4 x y :: IO a= - - =x :: b=, =y :: c= + #+begin_src haskell + - action1 :: IO b + - x :: b + - action2 x :: IO () + - action3 :: IO c + - y :: c + - action4 x y :: IO a + #+end_src - Few objects have the type =IO a=, this should help you choose. In particular you cannot use pure functions directly here. To use pure @@ -2108,7 +2252,8 @@ Ask a user to enter a list of numbers. Print the sum of the numbers. #+END_QUOTE -#+BEGIN_SRC haskell +{{{lnk(io_sum.hs)}}} +#+BEGIN_SRC haskell :tangle io_sum.hs toList :: String -> [Integer] toList input = read ("[" ++ input ++ "]") @@ -2172,7 +2317,7 @@ For example, what happens if the user enters something strange? Let's try: #+BEGIN_EXAMPLE -% runghc 02_progressive_io_example.lhs +[hs:hsenv]> runghc io_sum.hs Enter a list of numbers (separated by comma): foo Prelude.read: no parse @@ -2187,7 +2332,7 @@ In order to do this, we must detect that something went wrong. Here is one way to do this: use the type =Maybe=. This is a very common type in Haskell. -#+BEGIN_SRC haskell +#+BEGIN_SRC haskell :tangle io_sum_safe.hs import Data.Maybe #+END_SRC @@ -2208,7 +2353,7 @@ If the value is right, it returns =Just =. Don't try to understand too much of this function. I use a lower level function than =read=: =reads=. -#+BEGIN_SRC haskell +#+BEGIN_SRC haskell :tangle io_sum_safe.hs maybeRead :: Read a => String -> Maybe a maybeRead s = case reads s of [(x,"")] -> Just x @@ -2219,14 +2364,14 @@ Now to be a bit more readable, we define a function which goes like this: If the string has the wrong format, it will return =Nothing=. Otherwise, for example for "1,2,3", it will return =Just [1,2,3]=. -#+BEGIN_SRC haskell +#+BEGIN_SRC haskell :tangle io_sum_safe.hs getListFromString :: String -> Maybe [Integer] getListFromString str = maybeRead $ "[" ++ str ++ "]" #+END_SRC We simply have to test the value in our main function. -#+BEGIN_SRC haskell +#+BEGIN_SRC haskell :tangle io_sum_safe.hs main :: IO () main = do putStrLn "Enter a list of numbers (separated by comma):" @@ -2234,15 +2379,13 @@ We simply have to test the value in our main function. let maybeList = getListFromString input in case maybeList of Just l -> print (sum l) - Nothing -> error "Bad format. Good Bye." + Nothing -> putStrLn "Bad format. Good Bye." #+END_SRC In case of error, we display a nice error message. Note that the type of each expression in the main's =do= block remains of the form =IO a=. -The only strange construction is =error=. -I'll just say here that =error msg= takes the needed type (here =IO ()=). One very important thing to note is the type of all the functions defined so far. diff --git a/src/posts/0010-Haskell-Now/infinite_tree.hs b/src/posts/0010-Haskell-Now/infinite_tree.hs new file mode 100644 index 0000000..aa42d00 --- /dev/null +++ b/src/posts/0010-Haskell-Now/infinite_tree.hs @@ -0,0 +1,32 @@ +import Data.Tree (Tree,Forest(..)) +import qualified Data.Tree as Tree + +data BinTree a = Empty + | Node a (BinTree a) (BinTree a) + deriving (Eq,Ord,Show) + +-- | Function to transform our internal BinTree type to the +-- type of Tree declared in Data.Tree (from containers package) +-- so that the function Tree.drawForest can use +binTreeToForestString :: (Show a) => BinTree a -> Forest String +binTreeToForestString Empty = [] +binTreeToForestString (Node x left right) = + [Tree.Node (show x) ((binTreeToForestString left) ++ (binTreeToForestString right))] + +-- | Function that given a BinTree print a representation of it in the console +prettyPrintTree :: (Show a) => BinTree a -> IO () +prettyPrintTree = putStrLn . Tree.drawForest . binTreeToForestString + +nullTree = Node 0 nullTree nullTree + +-- take all element of a BinTree +-- up to some depth +treeTakeDepth _ Empty = Empty +treeTakeDepth 0 _ = Empty +treeTakeDepth n (Node x left right) = let + nl = treeTakeDepth (n-1) left + nr = treeTakeDepth (n-1) right + in + Node x nl nr + +main = prettyPrintTree (treeTakeDepth 4 nullTree) diff --git a/src/posts/0010-Haskell-Now/infinite_tree_2.hs b/src/posts/0010-Haskell-Now/infinite_tree_2.hs new file mode 100644 index 0000000..30aefc3 --- /dev/null +++ b/src/posts/0010-Haskell-Now/infinite_tree_2.hs @@ -0,0 +1,45 @@ +import Data.Tree (Tree,Forest(..)) +import qualified Data.Tree as Tree + +data BinTree a = Empty + | Node a (BinTree a) (BinTree a) + deriving (Eq,Ord,Show) + +-- | Function to transform our internal BinTree type to the +-- type of Tree declared in Data.Tree (from containers package) +-- so that the function Tree.drawForest can use +binTreeToForestString :: (Show a) => BinTree a -> Forest String +binTreeToForestString Empty = [] +binTreeToForestString (Node x left right) = + [Tree.Node (show x) ((binTreeToForestString left) ++ (binTreeToForestString right))] + +-- | Function that given a BinTree print a representation of it in the console +prettyPrintTree :: (Show a) => BinTree a -> IO () +prettyPrintTree = putStrLn . Tree.drawForest . binTreeToForestString + +-- | take all element of a BinTree up to some depth +treeTakeDepth _ Empty = Empty +treeTakeDepth 0 _ = Empty +treeTakeDepth n (Node x left right) = let + nl = treeTakeDepth (n-1) left + nr = treeTakeDepth (n-1) right + in + Node x nl nr + +iTree = Node 0 (dec iTree) (inc iTree) + where + dec (Node x l r) = Node (x-1) (dec l) (dec r) + inc (Node x l r) = Node (x+1) (inc l) (inc r) + +-- apply a function to each node of Tree +treeMap :: (a -> b) -> BinTree a -> BinTree b +treeMap f Empty = Empty +treeMap f (Node x left right) = Node (f x) + (treeMap f left) + (treeMap f right) + +infTreeTwo :: BinTree Int +infTreeTwo = Node 0 (treeMap (\x -> x-1) infTreeTwo) + (treeMap (\x -> x+1) infTreeTwo) + +main = prettyPrintTree $ treeTakeDepth 4 infTreeTwo diff --git a/src/posts/0010-Haskell-Now/io_sum.hs b/src/posts/0010-Haskell-Now/io_sum.hs new file mode 100644 index 0000000..7c7d7f5 --- /dev/null +++ b/src/posts/0010-Haskell-Now/io_sum.hs @@ -0,0 +1,7 @@ +toList :: String -> [Integer] +toList input = read ("[" ++ input ++ "]") + +main = do + putStrLn "Enter a list of numbers (separated by comma):" + input <- getLine + print $ sum (toList input) diff --git a/src/posts/0010-Haskell-Now/io_sum_safe.hs b/src/posts/0010-Haskell-Now/io_sum_safe.hs new file mode 100644 index 0000000..eb4eb4c --- /dev/null +++ b/src/posts/0010-Haskell-Now/io_sum_safe.hs @@ -0,0 +1,18 @@ +import Data.Maybe + +maybeRead :: Read a => String -> Maybe a +maybeRead s = case reads s of + [(x,"")] -> Just x + _ -> Nothing + +getListFromString :: String -> Maybe [Integer] +getListFromString str = maybeRead $ "[" ++ str ++ "]" + +main :: IO () +main = do + putStrLn "Enter a list of numbers (separated by comma):" + input <- getLine + let maybeList = getListFromString input in + case maybeList of + Just l -> print (sum l) + Nothing -> putStrLn "Bad format. Good Bye." diff --git a/src/posts/0010-Haskell-Now/shell.nix b/src/posts/0010-Haskell-Now/shell.nix index 8706197..f5d8732 100644 --- a/src/posts/0010-Haskell-Now/shell.nix +++ b/src/posts/0010-Haskell-Now/shell.nix @@ -19,4 +19,7 @@ in pkgs.stdenv.mkDerivation { name = "env"; buildInputs = nixPackages; + shellHook = '' + export PS1="\n[hs:\033[1;32m\]\W\[\033[0m\]]> " + ''; }