tl; dr :
A typical function that works withIO
looks almost like an imperative program:
f :: IO a f = do x <- action1 action2 x y <- action3 action4 xy
- To assign a value to an object, we use
<-
.- In this example, the type of expression in each line is
IO *
;
action1 :: IO b
action2 x :: IO ()
action3 :: IO c
action4 xy :: IO a
x :: b
,y :: c
- Several objects have type
IO a
.
With such objects, you cannot use pure functions.
In order to use pure functions, you will have to doaction2 (purefunction x)
.
Get a list of numbers from the user. Print their amount
toList :: String -> [Integer] toList input = read ("[" ++ input ++ "]") main = do putStrLn " ( ):" input <- getLine print $ sum (toList input)
putStrLn :: String -> IO () getLine :: IO String print :: Show a => a -> IO ()
do
block is of type IO a
? main = do putStrLn " ... " :: IO () getLine :: IO String print Something :: IO ()
<-
. do x <- something
something :: IO a
, then x :: a
.IO
. All lines in the do-block should look like one of two ways: action1 :: IO a -- a = ()
value <- action2 -- -- bar zt :: IO b -- value :: b
% runghc 02_progressive_io_example.lhs Enter a list of numbers (separated by comma): foo Prelude.read: no parse
Maybe
type. import Data.Maybe
Maybe
is a type that takes one parameter. Here is its definition: data Maybe a = Nothing | Just a
maybeRead
function is a great example of this approach.read
function (which is very similar to the javascript eval
function, which processes a JSON string.),Nothing
.Just <>
.read
; . maybeRead :: Read a => String -> Maybe a maybeRead s = case reads s of [(x,"")] -> Just x _ -> Nothing
Nothing
.Just [1,2,3]
. getListFromString :: String -> Maybe [Integer] getListFromString str = maybeRead $ "[" ++ str ++ "]"
main :: IO () main = do putStrLn " , :" input <- getLine let maybeList = getListFromString input in case maybeList of Just l -> print (sum l) Nothing -> error " . ."
IO a
.error
.error msg
function simply accepts any type of input ( IO ()
in our case).IO
and this: main
.getListFromString
function. 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 ++ "]"
askUser :: IO [Integer] askUser = do putStrLn " , :" input <- getLine let maybeList = getListFromString input in case maybeList of Just l -> return l Nothing -> askUser
IO [Integer]
.[Integer]
using IO actions."This is[Integer]
insideIO
"
main :: IO () main = do list <- askUser print $ sum list
IO
. Everything went very quickly. Here are a few things that I want you to remember:do
block, each expression must have type IO a
.getLine
, print
, putStrLn
, etc.IO a
means the following - IO action , which returns a result of type a
.IO
type represents actions, and IO a
is the type of the function.IO
will not be a problem for you.Exercises :
- Write a program that summarizes all your arguments. Hint: use the
getArgs
function.
tl; dr :
To distinguish pure functions,main
defined as a function that changes the state of the world
main :: World -> World
Functions with this type are guaranteed to have side effects. Look at a typical main function:
main w0 = let (v1,w1) = action1 w0 in let (v2,w2) = action2 v1 w1 in let (v3,w3) = action3 v2 w2 in action4 v3 w3
In this example, many temporary values ​​(w1
,w2
andw3
)
which are used to transfer data to the next action.
We write thebind
function or(>>=)
. Thanks tobind
we no longer need named temporary values.main = action1 >>= action2 >>= action3 >>= action4
Bonus: Haskell has syntax sugar for us:
main = do v1 <- action1 v2 <- action2 v1 v3 <- action3 v2 action4 v3
IO
? For now, it all looks like some kind of magic. askUser :: IO [Integer] askUser = do putStrLn " , :" input <- getLine let maybeList = getListFromString input in case maybeList of Just l -> return l Nothing -> askUser main :: IO () main = do list <- askUser print $ sum list
while
analog.IO
, the imperative style is more appropriate.main
function can potentially change the state of the world. And its type will look something like this: main :: World -> World
main
function. main :: World -> ((),World)
data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}
. All #
are related to optimization, and in the example I swapped several fields. But in general, the idea has not changed.)()
is an empty type. main w0 = let (list,w1) = askUser w0 in let (x,w2) = print (sum list,w1) in x
World -> (a,World)
a
is the type of result.getChar
function should be of type World -> (Char,World)
.fab
, you will have several options:a
, then b
then fab
b
, then a
and finally fab
.a
and b
and then fab
print
works like this:((),__)
. askUser :: World -> ([Integer],World)
askUser :: IO [Integer] askUser = do putStrLn " :" input <- getLine let maybeList = getListFromString input in case maybeList of Just l -> return l Nothing -> askUser
askUser w0 = let (_,w1) = putStrLn "Enter a list of numbers:" in let (input,w2) = getLine w1 in let (l,w3) = case getListFromString input of Just l -> (l,w2) Nothing -> askUser w2 in (l,w3)
w*
variables. let (y,w') = action xw in
x
not needed, the result will be a pair (answer, newWorldValue)
. Each f
function must have a type similar to: f :: World -> (a,World)
let (y,w1) = action1 w0 in let (z,w2) = action2 w1 in let (t,w3) = action3 w2 in ...
let (_,w1) = action1 x w0 in let (z,w2) = action2 w1 in let (_,w3) = action3 xz w2 in ...
actionN w :: (World) -> (a,World)
.IMPORTANT! There are only two patterns that are worth paying attention to:
let (x,w1) = action1 w0 in let (y,w2) = action2 x w1 in
and
let (_,w1) = action1 w0 in let (y,w2) = action2 w1 in
bind
two strings. To do this, write the function bind
. bind :: (World -> (a,World)) -> (a -> (World -> (b,World))) -> (World -> (b,World))
(World -> (a,World))
is the type for IO action. type IO a = World -> (a, World)
getLine :: IO String print :: Show a => a -> IO ()
getLine
is an IO action that takes the world as a parameter and returns a pair (String,World)
. You could say the type of getLine
would be an IO String
.print
function is also quite interesting. It takes a parameter as input, which it displays later. But in fact, it takes two parameters. The first parameter is the value that will be displayed, the second is the state of the world. As a result, it returns a pair ((),World)
. That is, it changes the state of the world, but does not return any data.bind
function: bind :: IO a -> (a -> IO b) -> IO b
bind
takes 2 IO actions as arguments and returns another IO action. let (x,w1) = action1 w0 in let (y,w2) = action2 x w1 in (y,w2)
action1 :: IO a action2 :: a -> IO b (y,w2) :: IO b
(bind action1 action2) w0 = let (x, w1) = action1 w0 (y, w2) = action2 x w1 in (y, w2)
let (line1,w1) = getLine w0 in let ((),w2) = print line1 in ((),w2)
(res,w2) = (bind getLine (\l -> print l)) w0
(World -> ((),World))
, we know that res = ()
(null type). let (line1,w1) = getLine w0 in let (line2,w2) = getLine w1 in let ((),w3) = print (line1 ++ line2) in ((),w3)
(res,w3) = bind getLine (\line1 -> bind getLine (\line2 -> print (line1 ++ line2)))
bind
with (>>=)
.(>>=)
is an infix function, the same as(+)
; remind 3 + 4 ⇔ (+) 3 4
(res,w3) = getLine >>= \line1 -> getLine >>= \line2 -> print (line1 ++ line2)
do x <- action1 y <- action2 z <- action3 ...
action1 >>= \x -> action2 >>= \y -> action3 >>= \z -> ...
x
in action2
and x
with y
in action3
.<-
?blindBind
function: blindBind :: IO a -> IO b -> IO b blindBind action1 action2 w0 = bind action (\_ -> action2) w0
(>>)
. do action1 action2 action3
action1 >> action2 >> action3
putInIO :: a -> IO a putInIO x = IO (\w -> (x,w))
putInIO
is called return
.return
very different from analogues in other languages. askUser :: IO [Integer] askUser = do putStrLn " , :" input <- getLine let maybeList = getListFromString input in case maybeList of Just l -> return l Nothing -> askUser main :: IO () main = do list <- askUser print $ sum list
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 ++ "]" askUser :: IO [Integer] askUser = putStrLn " , :" >> getLine >>= \input -> let maybeList = getListFromString input in case maybeList of Just l -> return l Nothing -> askUser main :: IO () main = askUser >>= \list -> print $ sum list
(>>)
and (>>=)
.IO
is a monad .do
notations.Important note :
- Monads are not necessarily related to side effects!
There are many pure monads.- The essence of monads - in the composition of calculations
Monad
is a type class.(>>=)
and return
.(>>)
will be created automatically based on (>>=)
.Monad
class: class Monad m where (>>=) :: ma -> (a -> mb) -> mb return :: a -> ma (>>) :: ma -> mb -> mb f >> g = f >>= \_ -> g -- -- fail :: String -> ma fail = error
Remarks:
- the
class
keyword is not what you think.
The class in Haskell is not a class from OOP.
A class in Haskell is more like an interface in Java or C #.
It would be better to call ittypeclass
.
What does a lot of classes mean?
For a type to belong to this class, the type must implement all the functions of the class.- In this particular case, the type
m
must be a type that can take an argument.
for example,IO a
, and alsoMaybe a
,[a]
, etc.- To be a useful monad, your function must comply with some laws.
If your function violates these laws, strange things will happen:
return a >>= k == ka m >>= return == m m >>= (\x -> kx >>= h) == (m >>= k) >>= h
Monad
instances.Maybe
.Maybe
values, you can use monads to work with them. In particular, it can be useful to get rid of nested if..then..else..
deposit value account = account + value withdraw value account = account - value eligible :: (Num a,Ord a) => a -> Bool eligible account = let account1 = deposit 100 account in if (account1 < 0) then False else let account2 = withdraw 200 account1 in if (account2 < 0) then False else let account3 = deposit 100 account2 in if (account3 < 0) then False else let account4 = withdraw 300 account3 in if (account4 < 0) then False else let account5 = deposit 1000 account4 in if (account5 < 0) then False else True main = do print $ eligible 300 -- True print $ eligible 299 -- False
deposit :: (Num a) => a -> a -> Maybe a deposit value account = Just (account + value) withdraw :: (Num a,Ord a) => a -> a -> Maybe a withdraw value account = if (account < value) then Nothing else Just (account - value) eligible :: (Num a, Ord a) => a -> Maybe Bool eligible account = do account1 <- deposit 100 account account2 <- withdraw 200 account1 account3 <- deposit 100 account2 account4 <- withdraw 300 account3 account5 <- deposit 1000 account4 Just True main = do print $ eligible 300 -- Just True print $ eligible 299 -- Nothing
deposit :: (Num a) => a -> a -> Maybe a deposit value account = Just (account + value) withdraw :: (Num a,Ord a) => a -> a -> Maybe a withdraw value account = if (account < value) then Nothing else Just (account - value) eligible :: (Num a, Ord a) => a -> Maybe Bool eligible account = deposit 100 account >>= withdraw 200 >>= deposit 100 >>= withdraw 300 >>= deposit 1000 >> return True main = do print $ eligible 300 -- Just True print $ eligible 299 -- Nothing
Maybe
will work in most imperative languages.Important note:
The first calculation, the result of which isNothing
will stop all further calculations.
This means that not all code will be executed.
And this optimization is available to you absolutely free, thanks to the laziness of the language.
(>>=)
for Maybe
instance Monad Maybe where (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b Nothing >>= _ = Nothing (Just x) >>= f = fx return x = Just x
Maybe
monad has proven its usefulness even in such a small example. We also saw the use of the IO
monad. But there is a more interesting example - lists. import Control.Monad (guard) allCases = [1..10] resolve :: [(Int,Int,Int)] resolve = do x <- allCases y <- allCases z <- allCases guard $ 4*x + 2*y < z return (x,y,z) main = do print resolve
[(1,1,7),(1,1,8),(1,1,9),(1,1,10),(1,2,9),(1,2,10)]
print $ [ (x,y,z) | x <- allCases, y <- allCases, z <- allCases, 4*x + 2*y < z ]
shuffle = map (\x -> (x*3123) `mod` 4331) [1..]
treeFromList
treeFromList :: (Ord a) => [a] -> BinTree a treeFromList [] = Empty treeFromList (x:xs) = Node x (treeFromList (filter (<x) xs)) (treeFromList (filter (>x) xs))
treeTakeDepth
: 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 = do putStrLn "take 10 shuffle" print $ take 10 shuffle putStrLn "\ntreeTakeDepth 4 (treeFromList shuffle)" print $ treeTakeDepth 4 (treeFromList shuffle)
% runghc 02_Hard_Part/41_Infinites_Structures.lhs take 10 shuffle [3123,1915,707,3830,2622,1414,206,3329,2121,913] treeTakeDepth 4 (treeFromList shuffle) < 3123 : |--1915 : | |--707 : | | |--206 : | | `--1414 : | `--2622 : | |--2121 : | `--2828 : `--3830 : |--3329 : | |--3240 : | `--3535 : `--4036 : |--3947 : `--4242
treeTakeDepth 4 (treeFromList [1..])
filter (<1) [2..]
.filter
not smart enough to understand that the result of the expression will be an empty list.n
that treeTakeDepth n (treeFromList shuffle)
will fall into an infinite loop.n
.shuffle
list, executing which, the program will end.treeFromList
and shuffle
in order to get rid of this problem.shuffle
.4331
different numbers.shuffle
. shuffle = map rand [1..] where rand x = ((px) `mod` (x+c)) - ((x+c) `div` 2) px = m*x^2 + n*x + o -- some polynome m = 3123 n = 31 o = 7641 c = 1237
filter (<x) xs
.Any element of the left (or right) must be strictly less (or greater) than the value in the root of the tree.
treeFromList
we just replaced it filter
with safefilter
. treeFromList :: (Ord a, Show a) => [a] -> BinTree a treeFromList [] = Empty treeFromList (x:xs) = Node x left right where left = treeFromList $ safefilter (<x) xs right = treeFromList $ safefilter (>x) xs
safefilter
almost identical filter
but does not fall into an infinite loop when processing infinite trees. If she cannot find a suitable element in 10,000 steps, she interrupts the search. safefilter :: (a -> Bool) -> [a] -> [a] safefilter fl = safefilter' fl nbTry where nbTry = 10000 safefilter' _ _ 0 = [] safefilter' _ [] _ = [] safefilter' f (x:xs) n = if fx then x : safefilter' f xs nbTry else safefilter' f xs (n-1)
main = do putStrLn "take 10 shuffle" print $ take 10 shuffle putStrLn "\ntreeTakeDepth 8 (treeFromList shuffle)" print $ treeTakeDepth 8 (treeFromList $ shuffle)
8
to 100
, thedeep
and nbTry
, the function works fine. But in the worst case, we can expect an exponential growth.treeFromList
.[0,-1,-1,....,-1,1,-1,...,-1,1,...]
).safefilter
as follows:safefilter' fl = if filter f (take 10000 l) == [] then [] else filter fl
shuffle
.safefilter'
)f
treeFromList' [] n = Empty treeFromList' (x:xs) n = Node x left right where left = treeFromList' (safefilter' (<x) xs (fn) right = treeFromList' (safefilter' (>x) xs (fn) f = ???
/r/haskell
u/r/programming
.Source: https://habr.com/ru/post/153383/
All Articles