Difference between revisions of "Ru/IO"

From HaskellWiki
< Ru
Jump to navigation Jump to search
(еще немого маркап)
(неможко из IO_inside)
Line 12: Line 12:
   
 
Простейшая процедура, выполняющая два действия. Как мы уже говорили, они выполняются строго в заданном порядке:
 
Простейшая процедура, выполняющая два действия. Как мы уже говорили, они выполняются строго в заданном порядке:
  +
 
<haskell>
 
<haskell>
 
main = do print "Hello, World!"
 
main = do print "Hello, World!"
Line 28: Line 29:
   
 
Аналогичным образом можно применять case:
 
Аналогичным образом можно применять case:
  +
 
<haskell>
 
<haskell>
 
...
 
...
Line 53: Line 55:
 
else do print ("Hi, "++name)
 
else do print ("Hi, "++name)
 
</haskell>
 
</haskell>
 
   
 
Совершенно аналогично функциям, процедуры могут иметь входные параметры. Для описания чистых вычислений внутри процедур можно использовать ''let''-блоки, однако вычисления в ''let''-блоке не могут ссылаться на имена, определённые в нижеследующих действиях или ''let''-блоках:
 
Совершенно аналогично функциям, процедуры могут иметь входные параметры. Для описания чистых вычислений внутри процедур можно использовать ''let''-блоки, однако вычисления в ''let''-блоке не могут ссылаться на имена, определённые в нижеследующих действиях или ''let''-блоках:
Line 72: Line 73:
   
 
А теперь забацаем пример, который включает все вышеприведённые извраты:
 
А теперь забацаем пример, который включает все вышеприведённые извраты:
  +
 
<haskell>
 
<haskell>
 
...
 
...
Line 92: Line 94:
 
== Немного глубже в 'do' ==
 
== Немного глубже в 'do' ==
   
Поначалу кажется, что '''do''' - какая-то чёрная магия, или встроенный синтаксис для грязных процедур. На самом деле всё гораздо интереснее. Рассмотрим сперва cтруктуру блока:
+
Поначалу кажется, что '''do''' - какая-то чёрная магия, или встроенный синтаксис для грязных процедур. На самом деле всё это просто cинтаксический сахар, без которого можно обойтись, но который позволяет склеивать процедуры и делает их написание проще.
   
  +
Для одного выражения '''do''' излишне:
<haskell>
 
main = do proc1
 
proc2
 
x <- proc3
 
proc4 x
 
proc5
 
return (x+1)
 
</haskell>
 
   
  +
<haskell>main = do putStr "Hello!"</haskell>
В нём могут быть вызовы процедур, типа ''proc1'' .. ''proc6'', или что-то вроде присваивания (''x <- proc3''), ниже которого доступно новое значение (''x'').
 
   
  +
без '''do''' можно записать как:
На деле Haskell сворачивает '''do''' блок и сводит его к нескольким операторам: ''(>>)'', ''(>>=)'' и ''return''. Первый из них означает последовательность:
 
   
<haskell>
+
<haskell>main = putStr "Hello!"</haskell>
main1 = do proc1
 
proc2
 
   
  +
Как же обойтись без '''do''' для нескольких действий?
main2 = proc1 >> proc2 -- то же самое!
 
  +
 
<haskell>
  +
main = do putStr "Как Вас зовут?"
  +
putStr "Сколько Вам лет?"
  +
putStr "Неплохой денёк сегодня!"
 
</haskell>
 
</haskell>
   
  +
Тут '''do''' говорит, что действия идут одно за другим. Это переводится в оператор последовательности ''(>>)'':
А вот второй используется для "присваиваний". На самом деле каждое выражение ''(x <- proc3)'' и весь последующий "хвост" превращается в анонимную функцию (лямбду) с параметром x, и приклеивается к голове с помощью ''(>>=)''. Вспомним как пишутся лямбды ''(\x -> x + 1)'' и внимательно переведём наш пример, расставляя необязательные скобки:
 
   
 
<haskell>
 
<haskell>
  +
main = (putStr "Как Вас зовут?") >> (putStr "Сколько Вам лет?") >> (putStr "Неплохой денёк сегодня!")
main1 = do proc1
 
 
</haskell>
proc2
 
x <- proc3
 
proc4 x
 
proc5
 
return (x+1)
 
main2 = proc1 >> proc2 >>= (\x -> proc3 >> proc4 x >> proc5 >> (return (x+1)))
 
   
  +
Более сложными являются примеры с ''(<-)'':
-- main1 - то же, что и main2
 
  +
  +
<haskell>
 
main = do a <- readLn
 
print a
 
</haskell>
 
</haskell>
   
  +
Такой код переводится в:
В контексте процедур, ''x >> y'' означает нечто вроде "сначала делать x, потом y".
 
   
  +
<haskell>main = readLn >>= (\a -> print a)</haskell>
А выражения типа ''(x >>= (\y -> z))'' означает "сделать x, взять результат х, подставить его в лямбду вместо y, вычислить z, и сделать z".
 
  +
 
Выражения типа ''(x >>= (\y -> z))'' означает "сделать x, взять его результат, подставить его в лямбду вместо y, вычислить z, и сделать z".
  +
  +
С этим оператором можно писать, к примеру, и так:
  +
  +
<haskell>main = readLn >>= print</haskell>
   
 
Зачем это всё знать? А все дело в том, что все эти операторы (и '''do''') используются не только в процедурах, а и, к примеру, в массивах. Их смысл можно даже переопределять!
 
Зачем это всё знать? А все дело в том, что все эти операторы (и '''do''') используются не только в процедурах, а и, к примеру, в массивах. Их смысл можно даже переопределять!
   
 
Подробнее о массивах:
 
Подробнее о массивах:
  +
 
<haskell>
 
<haskell>
 
a1 = do x <- [10,100,1000] -- перебрать все эти числа как x
 
a1 = do x <- [10,100,1000] -- перебрать все эти числа как x

Revision as of 15:00, 18 September 2007

Ввод и вывод - это грязь

В императивных языках вроде C++ нет разделения функций на чистые и имеющие побочные эффекты, любая функция рассматривается как потенциально "грязная". С одной стороны, это облегчает модификацию программы (любая чисто вычислительная функция может быть переделана в имеющую побочные эффекты), с другой стороны - усложняет понимание программы, её отладку и модификацию. Какая-нибудь скромная функция sin может иметь совершенно нескромные побочные эффекты, например стереть системные файлы.

В отличие от них, в Haskell все функции чётко поделены на два класса, и чистые функции не могут вызыыать нечистые. Для удобства дальнейшего изложения давайте условимся называть чистые функции просто функциями, а нечистые - процедурами. Итак, функция - это просто однозначный способ вычисления выходного значения по входным, а процедура выполняет некоторое действие (и может иметь выходное значение).

Вычисления внутри функций производятся по мере необходимости и в том порядке, в каком в них возникает необходимость. В отличие от этого процедура описывает последовательность операций, которые выполняются обязательно и обязательно в указанном порядке. Поэтому способ записи, применяемый для определения функций, не годится для процедур, и для них используется специальная do-нотация, сходная с императивными языками (как С++ или Python).

Процедуры в Haskell

Главная выполняемая функция в программе на Haskell - main - является процедурой, и на ней мы рассмотрим примеры описания процедур.

Простейшая процедура, выполняющая два действия. Как мы уже говорили, они выполняются строго в заданном порядке:

main = do print "Hello, World!"
          print "Haskell rules, C++ sucks!"

"Действия", выполнямые в do - это в свою очередь вызовы других процедур. Мы также можем присваивать "переменным" значения, возвращаемые из этих процедур, и организовывать условное выполнение:

main = do print "Hey, kid, what is your name?"
          name <- getLine
          if name=="Bulat"
            then do print "Thank you, Creator"
            else do print ("Hi, "++name)

Аналогичным образом можно применять case:

...

Для организации циклов, как обычно, используется рекурсия.

Возвратить значение из порцедуры можно с помощью "return". Опишем процедуру, которая вводит непустую строку:

myGetLine = do str <- getLine
               if str==""
                 then do print "Пожалуйств, введите непустую строку"
                         myGetLine
                 else return str

и пример её применения:

main = do print "Hey, kid, what is your name?"
          name <- myGetLine
          if name=="Bulat"
            then do print "Thank you, Creator"
            else do print ("Hi, "++name)

Совершенно аналогично функциям, процедуры могут иметь входные параметры. Для описания чистых вычислений внутри процедур можно использовать let-блоки, однако вычисления в let-блоке не могут ссылаться на имена, определённые в нижеследующих действиях или let-блоках:

math x y = do 
  let x2 = x*2
      x3 = x2*x
      xy = x*y
  print ("x=" ++ (show x))
  print ("x в квадрате=" ++ (show x2))
  print ("x в кубе=" ++ (show x3))
  print ("произведение x и y=" ++ (show xy))

main = do math 2 2
          math 3 4

А теперь забацаем пример, который включает все вышеприведённые извраты:

...

Процедуры как параметры

Наконец, давайте вернёмся к истокам и вспомним, что "процедуры" в хаскеле - это всего лишь грязные функции, которые могут иметь побочные эффекты, а функции в хаскеле являются "первоклассными" значениями. Это значит, что процедуры, как и любые другие функции, можно передавать в качестве параметров, сохранять в структурах данных, "добивать" параметрами. Различие всего одно - функция, применённая ко всем своим параметрами, является уже значением - это значение может храниться невычисленным только благодаря lazy evaluation. Процедура же, даже со всеми своими параметрами, остаётся процедурой (или если хотите действием) и выполняется ровно в тот момент, когда она вызвано в do-нотации. Пример:

main = do example (print "Hi")

example action = do print "Before"
                    action
                    print "After"

Здесь "Hi" печатается не в момент вызова example (для него операция печати - это всего лишь пассивный параметр), а в тот момент, когда вызов action вставлен в do-последовательность.

Немного глубже в 'do'

Поначалу кажется, что do - какая-то чёрная магия, или встроенный синтаксис для грязных процедур. На самом деле всё это просто cинтаксический сахар, без которого можно обойтись, но который позволяет склеивать процедуры и делает их написание проще.

Для одного выражения do излишне:

main = do putStr "Hello!"

без do можно записать как:

main = putStr "Hello!"

Как же обойтись без do для нескольких действий?

main = do putStr "Как Вас зовут?"
          putStr "Сколько Вам лет?"
          putStr "Неплохой денёк сегодня!"

Тут do говорит, что действия идут одно за другим. Это переводится в оператор последовательности (>>):

main = (putStr "Как Вас зовут?") >> (putStr "Сколько Вам лет?") >> (putStr "Неплохой денёк сегодня!")

Более сложными являются примеры с (<-):

main = do a <- readLn
          print a

Такой код переводится в:

main = readLn >>= (\a -> print a)

Выражения типа (x >>= (\y -> z)) означает "сделать x, взять его результат, подставить его в лямбду вместо y, вычислить z, и сделать z".

С этим оператором можно писать, к примеру, и так:

main = readLn >>= print

Зачем это всё знать? А все дело в том, что все эти операторы (и do) используются не только в процедурах, а и, к примеру, в массивах. Их смысл можно даже переопределять!

Подробнее о массивах:

a1 = do x <- [10,100,1000]  -- перебрать все эти числа как x
        y <- [1,2,3]        -- перебрать 1..3 как y
        return (x*y)        -- слить (x*y) в массив-результат

-- Результат: [10,20,30,100,200,300,1000,2000,3000]

Но об этих тонкостях лучше прочитать в статье о монадах.

Ссылки