Я считаю это полуправдой. 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 довольно многословным и неудобным для использования в императивном стиле. Вот пример вычисления в императивном стиле с использованием вышеуказанного цикла 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 в императивном стиле просто не «течет», как хорошо продуманный императивный язык, например питон.
В заключение, с синтаксической подтяжкой лица, Haskell вполне может быть лучшим императивным языком. Но по характеру подтяжки лица это должно было бы заменить что-то внутренне красивое и настоящее чем-то внешне красивым и поддельным.
РЕДАКТИРОВАТЬ : Контраст lastElt
с этой транслитерацией питона:
def last_elt(xs):
assert xs, "Empty list!!"
lst = xs
ret = xs.head
while lst:
ret = lst.head
lst = lst.tail
return ret
Одинаковое количество строк, но в каждой строке уровень шума немного меньше.
РЕДАКТИРОВАТЬ 2
Что стоит, вот как выглядит замена pure в 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"
Оператор (.)
- это композиция функций .