Считаю это полуправдой. Haskell обладает удивительной способностью к абстракции, в том числе абстракцией над императивными идеями. Например, в Haskell нет встроенного императивного цикла while, но мы можем просто написать его, и теперь он есть:
while :: (Monad m) => m Bool -> m () -> m ()
while cond action = do
c <- cond
if c
then action >> while cond action
else return ()
Такой уровень абстракции труден для многих императивных языков. Это можно сделать в императивных языках с замыканиями; например. Python и C #.
Но Haskell также обладает (весьма уникальной) способностью характеризовать разрешенные побочные эффекты , используя классы Monad. Например, если у нас есть функция:
foo :: (MonadWriter [String] m) => m Int
Это может быть «императивная» функция, но мы знаем, что она может делать только две вещи:
- «Вывести» поток строк
- вернуть Int
Он не может печатать на консоли или устанавливать сетевые соединения и т. Д. В сочетании с возможностью абстракции вы можете писать функции, которые действуют на «любые вычисления, производящие поток» и т. Д.
На самом деле все дело в абстракционных способностях Haskell, которые делают его очень хорошим императивным языком.
Однако ложная половина - это синтаксис. Я считаю Haskell довольно многословным и неудобным для использования в императивном стиле. Вот пример вычисления в императивном стиле с использованием вышеуказанного while
цикла, который находит последний элемент связанного списка:
lastElt :: [a] -> IO a
lastElt [] = fail "Empty list!!"
lastElt xs = do
lst <- newIORef xs
ret <- newIORef (head xs)
while (not . null <$> readIORef lst) $ do
(x:xs) <- readIORef lst
writeIORef lst xs
writeIORef ret x
readIORef ret
Весь этот мусор IORef, двойное чтение, необходимость привязать результат чтения, fmapping ( <$>
) для работы с результатом встроенного вычисления ... все это выглядит очень сложно. Это имеет большой смысл с функциональной точки зрения, но императивные языки, как правило, скрывают большинство этих деталей, чтобы упростить их использование.
По общему признанию, возможно, если бы мы использовали while
комбинатор другого стиля, он был бы чище. Но если вы зайдете в эту философию достаточно далеко (используя богатый набор комбинаторов, чтобы четко выразить себя), вы снова придете к функциональному программированию. Haskell в императивном стиле просто не «течет», как хорошо разработанный императивный язык, например, python.
В заключение, с синтаксической подтяжкой лица Haskell вполне может быть лучшим императивным языком. Но по своей природе подтяжки лица заменят что-то внутренне красивое и настоящее на что-то внешне красивое и фальшивое.
РЕДАКТИРОВАТЬ : Сравните lastElt
с этой транслитерацией python:
def last_elt(xs):
assert xs, "Empty list!!"
lst = xs
ret = xs.head
while lst:
ret = lst.head
lst = lst.tail
return ret
Такое же количество строк, но каждая строка имеет немного меньше шума.
ИЗМЕНИТЬ 2
Как бы то ни было, вот как выглядит чистая замена в Haskell:
lastElt = return . last
Вот и все. Или, если вы запретите мне использовать Prelude.last
:
lastElt [] = fail "Unsafe lastElt called on empty list"
lastElt [x] = return x
lastElt (_:xs) = lastElt xs
Или, если вы хотите, чтобы он работал с любой Foldable
структурой данных и понимал, что вам на самом деле не нужно IO
обрабатывать ошибки:
import Data.Foldable (Foldable, foldMap)
import Data.Monoid (Monoid(..), Last(..))
lastElt :: (Foldable t) => t a -> Maybe a
lastElt = getLast . foldMap (Last . Just)
с Map
, например:
λ➔ let example = fromList [(10, "spam"), (50, "eggs"), (20, "ham")] :: Map Int String
λ➔ lastElt example
Just "eggs"
(.)
Оператор функциональной композиции .