Тут приведено несколько решений задачи: нарисуйте прямоугольник M×N
, где M
и N
– числа, полученные программой на вход.
Везде действие main
реализовано так:
main = do
text <- getContents
let m:n:_ = read <$> words text :: [Int]
process m n
Сначала решение вида “цикл в цикле”, полный аналог следующего кода
void process(long m, long n) {
for (long i = 0; i < m; i++) {
for (long j = 0; j < n; j++) {
std::cout << "*";
}
std::cout << std::endl;
}
}
Из Хаскелла получается плохой Си, но всё же…
process m n = loopOuter 0
where
loopOuter i =
| i < m = loopInner 0 >> loopOuter (i+1)
| True = pure ()
loopInner j =
| j < n = putStr "*" >> loopInner (j+1)
| True = putStrLn ""
Теперь немного более “функциональное” решение:
-- Используются стандартные функции:
-- replicate n x = take n $ repeat x
-- sequence_ [] = pure ()
-- sequence_ (x:xs) = x >> sequence_ xs
process m n =
let
picture = replicate m $ replicate n '*'
actions = putStrLn <$> picture
in sequence_ actions
case
На самом деле, определение функции при помощи нескольких уравнений, как и многие другие части языка, тоже является сокращением. А именно, сокращением для более примитивной конструкции case
. Синтаксис у этой конструкции такой:
case выражение of
шаблон_1 -> результат_1
шаблон_2 -> результат_2
...
шаблон_n -> результат_n
Например, вот два эквивалентных определения функции, определяющий, является ли длина списка чётной (аналог even . length
; точкой обозначается композиция):
evenLength1 (a:b:rest) = evenLength1 rest
evenLength1 (a:[]) = False
evenLength1 [] = True
evenLength2 list = case list of
a:b:rest -> evenLength2 rest
a:[] -> False
[] -> True
Несколько уравнений хороши в том случае, когда приходится вести индукцию по более чем одному аргументу. Конструкция case же удобнее тем, что она является выражением в отличие от определения фунции.
С case и определениями связана одна важная тонкость: шаблонные определения констант ведут себя немного по-другому
совершенноОбычнаяЕдиница =
let
(a,b) = undefined
in 1
ошибкаПриПопыткеВычисления =
case undefined of
(a,b) -> 1
А именно, вычисление a и b в первом из этих определений происходит только тогда, когда требуется значение a или b. Во втором же случае вычисление a и b происходит в момент вычисления всей case
-конструкции.
Тем не менее, было бы странно, если бы определения констант не сводились к case
-конструкции. И действительно, шаблон case
-конструкции можно пометить тильдой, отложив сравнение с шаблоном до того момента, как a и b понадобятся:
совершенноОбычнаяЕдиница =
case undefined of
~(a,b) -> 1
IO a
На самом деле, никакой особо сильной магии в определении типа IO a
нет. Одна из его возможных реализаций (к сожалению, скомпилировать её именно в таком виде по ряду причин не получится):
-- словом type в Haskell объявляются синонимы типов
-- (аналог using из C++ или typedef из C)
type IO a = RealWorld -> (a, RealWorld)
pure :: a -> IO a
pure x = \world -> (x,world)
(>>=) :: IO a -> (a -> IO b) -> IO b
action >>= generator = \world0 ->
case action world0 of
(value,world1) -> generator value world1
При этом автономная программа работает так, как будто в main
в качестве величины типа RealWorld
подставили состояние окружающего мира (точнее, той его части, с которой программа может непосредственно взаимодействовать) на момент запуска программы.
getContents
плохо сочетается с интерпретатором. Причина такова: стандартная реализация getContents
получает очередной символ только тогда, когда он кому-то требуется, запрещая при этом кому-либо другому читать данные из входного потока. Интерпретатор же периодически взаимодействует со входным потоком, а после выполнения getContents
такое взаимодействие приводит к ошибке.Реализуйте операцию getContents'
, которая будет полностью получать всё содержимое стандартного входа перед дальнейшей обработкой. Вам могут пригодиться действия getChar
(получает очередной символ) и isEOF
(определяет, можно ли ещё раз сделать getChar
). Чтобы воспользоваться isEOF
, в начале программы (после строчки с module
) нужно написать:
import System.IO(isEOF)
import System.IO(isEOF)
getContents' = do
eof <- isEOF
case eof of
True -> pure []
False -> do
x <- getChar
xs <- getContents'
pure $ x:xs
-- простая проверка
main = getContents' >>= putStrLn
Напишите программу, которая получает на вход два числа N
и K
и рисует подряд K
полых квадратов размера N×N
. Например для N=3
и K=4
программа должна напечатать
*** *** *** ***
* * * * * * * *
*** *** *** ***
squareRow size count row
| row == 1 || row == size = concat $ replicate count $ replicate size '*' ++ " "
| True = concat $ replicate count $ "*" ++ replicate (size-2) ' ' ++ "* "
main = do
contents <- getContents
let numbers :: [Int]
numbers = read <$> words contents
size:count:_ = numbers
sequence_ $ (putStrLn . squareRow size count) <$> [1..size]
process (x1:x2:xs)
| x1 == x2 = process (x2:xs)
| True = x1 : process (x2:xs)
process x = x
main = getLine >>= (putStrLn . process)
Напишите программу, которая получает на вход строчку текста, после чего для каждой буквы, которая в этой строчке встречается, выводит количество раз, которое эта буква встречается. Например, для строчки “ааааббабаав” программа должна напечатать
а -> 7
б -> 3
в -> 1
type CountList = [(Char,Integer)]
update :: Char -> CountList -> CountList
update x ((y,n):rest)
| x == y = (y,n+1) : rest
| True = (y,n) : update x rest
update x [] = [(x,1)]
displayCount :: (Char,Integer) -> String
displayCount (x,n) = (x : " -> ") ++ show n
process :: String -> CountList
process "" = []
process (x:xs) = update x $ process xs
main = do
line <- getLine
sequence_ $ (putStrLn . displayCount) <$> process line
@ 2016 arbrk1, all rights reversed