This commit is contained in:
Yann Esposito (Yogsototh) 2019-12-25 22:17:22 +01:00
parent 7784f02483
commit cc7ee03907
Signed by untrusted user who does not match committer: yogsototh
GPG key ID: 7B19A4C650D59646
7 changed files with 324 additions and 72 deletions

View file

@ -0,0 +1,4 @@
fib :: [Integer]
fib = 1:1:zipWith (+) fib (tail fib)
main = traverse_ print (take 20 (drop 200 fib))

View file

@ -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 <the value>=.
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.

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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."

View file

@ -19,4 +19,7 @@ in
pkgs.stdenv.mkDerivation {
name = "env";
buildInputs = nixPackages;
shellHook = ''
export PS1="\n[hs:\033[1;32m\]\W\[\033[0m\]]> "
'';
}