
tl;dr:
Типичная функция, работающая сIOвыглядит практически как императивная программа:
f :: IO a f = do x <- action1 action2 x y <- action3 action4 x y
- Для присвоения значения объекту мы используем
<-.- В этом примере типом выражения в каждой строке является
IO *;
action1 :: IO baction2 x :: IO ()action3 :: IO caction4 x y :: IO ax :: b,y :: c
- Несколько объектов имеет тип
IO a.
С такими объектами вы не можете использовать чистые функции.
Для того, чтобы использовать чистые функции придется сделатьaction2 (purefunction x).
Получить от пользователя список чисел. Напечатать их сумму
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 имеет тип IO a?main = do
putStrLn "Введите ... " :: IO ()
getLine :: IO String
print Something :: IO ()
<-.do
x <- something
something :: IO a, то x :: a.IO. Все строки в do-блоке должны выглядеть одним из двух способов:action1 :: IO a
-- в общем случае это будет a = ()
value <- action2 -- где
-- bar z t :: IO b
-- value :: b
% runghc 02_progressive_io_example.lhs
Enter a list of numbers (separated by comma):
foo
Prelude.read: no parse
Maybe.import Data.Maybe
Maybe это тип, принимающий один параметр. Вот его определение:data Maybe a = Nothing | Just a
maybeRead — прекрасный пример подобного подхода. read (что очень похоже на javascript функцию eval, которая обрабатывает JSON-строку.),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 просто принимает на вход любой тип (IO () в нашем случае).IO и это: main. getListFromString.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] используя IO действия.«Это[Integer]внутриIO»
main :: IO ()
main = do
list <- askUser
print $ sum list
IO. Все прошло очень быстро. Вот несколько вещей, которые я хочу, чтобы вы запомнили:do каждое выражение должно иметь тип IO a.getLine, print, putStrLn, и т.п.IO a означает следующее — IO действие, которое возвращает результат типа a.IO тип для представления действий, а IO a — это тип функции.IO не будет для вас проблемой.Упражнения:
- Напишите программу, которая суммирует все свои аргументы. Подсказка: воспользуйтесь функцией
getArgs.

tl;dr:
Чтобы отличать чистые функции,
mainопределена как функция, которая меняет состояние мира
main :: World -> World
Функции с этим типом гарантированно будут иметь побочные эффекты. Посмотрите на типичную main-функцию:
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
В этом примере много временных значений (w1,w2иw3)
которые используются для передачи данных следующему действию.
Мы пишем функциюbindили(>>=). Благодаряbindнам больше не понадобятся именованные временные значения.
main = action1 >>= action2 >>= action3 >>= action4
Бонус: Haskell припас для нас синтаксический сахар:
main = do v1 <- action1 v2 <- action2 v1 v3 <- action3 v2 action4 v3
IO? Пока все это выглядит как какая-то магия.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 на Haskell.IO, императивный стиль подходит больше.main потенциально может изменить состояние мира. И ее тип будет выглядеть примерно так:main :: World -> World
main.main :: World -> ((),World)
data IO a = IO {unIO :: State# RealWorld -> (# State# RealWorld, a #)}. Все # связаны с оптимизацией, и в примере я поменял местами несколько полей. Но в целом, идея не поменялась.)() — это пустой тип.main w0 =
let (list,w1) = askUser w0 in
let (x,w2) = print (sum list,w1) in
x
World -> (a,World)
a — тип результата.getChar должна иметь тип World -> (Char,World).f a b, у вас будет несколько вариантов:a, потом b потом f a bb, потом a и в завершение f a b.a и b, а затем f a bprint работает примерно так:((),новый_идентификатор_мира).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*.let (y,w') = action x w in
x не нужен, результатом будет пара (answer, newWorldValue). Каждая функция f должна иметь тип, похожий на: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 x z w2 in
...
actionN w :: (World) -> (a,World).ВАЖНО! есть только два паттерна, на которые стоит обратить внимание:
let (x,w1) = action1 w0 in let (y,w2) = action2 x w1 in
и
let (_,w1) = action1 w0 in let (y,w2) = action2 w1 in

bind) две строки. Для этого напишем функцию bind.bind :: (World -> (a,World))
-> (a -> (World -> (b,World)))
-> (World -> (b,World))
(World -> (a,World)) это тип для IO действия.type IO a = World -> (a, World)
getLine :: IO String
print :: Show a => a -> IO ()
getLine это IO действие, которое получает мир в качестве параметра и возвращает пару (String,World). Можно сказать, типом getLine будет IO String.print тоже достаточно интересна. Она принимает на вход параметр, который потом отобразит. Но на самом деле она принимает два параметра. Первым параметром идет значение, которое будет отображаться, вторым — состояние мира. В качестве результата она возвращает пару ((),World). То есть она изменяет состояние мира, но не возвращает каких-либо данных.bind:bind :: IO a
-> (a -> IO b)
-> IO b
bind принимает 2 IO действия в качестве аргументов и возвращает еще одно IO действие.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)), мы знаем, что 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 на (>>=).(>>=) это инфиксная функция, такая же, как (+); напоминаю 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 в action2 и x с y в action3.<-?blindBind: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 называют return.return очень отличается от аналогов в других языках.
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
(>>) и (>>=).
IO — это монада.do нотации.Важное замечение:
- Монады не обязательно связаны с побочными эффектами!
Существует множество чистых монад.- Суть монад — в композиции вычислений
Monad это класс типов.(>>=) и return.(>>) будет создана автоматически на основе (>>=).Monad :class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
(>>) :: m a -> m b -> m b
f >> g = f >>= \_ -> g
-- Эту функцию в большинстве случаев вы можете игнорировать
-- я думаю она существует только как историческое наследие
fail :: String -> m a
fail = error
Замечания:
- ключевое слово
classэто не то, что вы думаете.
Класс в Haskell это не класс из ООП.
Класс в Haskell больше похож на интерфейс в Java или C#.
Лучше было бы его назватьtypeclass.
Что значит множество классов.
Чтобы тип мог принадлежать этому классу, тип должен реализовать все функции класса.- В данном конкретном случае тип
mдолжен быть типом, который может принимать аргумент.
напримерIO a, а такжеMaybe a,[a], и т.д.- Чтобы быть полезной монадой, ваша функция должна соблюдать некоторые законы.
Если ваша функция будет нарушать эти законы, будут происходить странные вещи:
return a >>= k == k a m >>= return == m m >>= (\x -> k x >>= h) == (m >>= k) >>= h
Monad.Maybe.Maybe значений, вы можете воспользоваться монадами для работы с ними. В частности это может быть полезно, чтобы избавиться от вложенных 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 сработает в большинстве императивных языков.Важное замечание:
Первое вычисление, результатом которого будетNothingостановит все дальнейшие вычисления.
Это значит, что выполнится не весь код.
И эта оптимизация доступна вам абсолютно бесплатно, благодаря ленивости языка.
(>>=) для Maybeinstance Monad Maybe where
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= _ = Nothing
(Just x) >>= f = f x
return x = Just x
Maybe доказала свою полезность даже в таком маленьком примере. Мы также видели использование монады IO. Но есть и более интересный пример — списки.
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..]
treeFromListtreeFromList :: (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 не настолько умен, чтобы понять, что результатом выражения будет пустой список.n, что treeTakeDepth n (treeFromList shuffle) упадет в бесконечный цикл.n.shuffle списка, выполняя который, программа завершится.treeFromList и shuffle для того, чтобы избавиться от этой проблемы.shuffle.4331 различных чисел.shuffle.shuffle = map rand [1..]
where
rand x = ((p x) `mod` (x+c)) - ((x+c) `div` 2)
p x = m*x^2 + n*x + o -- some polynome
m = 3123
n = 31
o = 7641
c = 1237
filter (<x) xs.Любой элемент левой (соотв. правой) должен быть строго меньше (соотв. больше) значения в корне дерева.
treeFromList мы просто заменили filter на 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 почти идентична filter но не падает в бесконечный цикл при обработке бесконечных деревьев. Если она не может найти подходящий элемент за 10000 шагов, она прерывает поиск.safefilter :: (a -> Bool) -> [a] -> [a]
safefilter f l = safefilter' f l nbTry
where
nbTry = 10000
safefilter' _ _ 0 = []
safefilter' _ [] _ = []
safefilter' f (x:xs) n =
if f x
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 до 100,deep и nbTry, функция работает нормально.Но в самом худшем случае нас может ожидать экспоненциальный рост.treeFromList.[0,-1,-1,....,-1,1,-1,...,-1,1,...]).safefilter следующим образом:safefilter' f l = if filter f (take 10000 l) == []
then []
else filter f l
shuffle это список случайных значений.safefilter' напрямую)f которая сгенерирует бесконечный список послеtreeFromList' [] n = Empty
treeFromList' (x:xs) n = Node x left right
where
left = treeFromList' (safefilter' (<x) xs (f n)
right = treeFromList' (safefilter' (>x) xs (f n)
f = ???
/r/haskell и/r/programming.Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.
комментарии (8)