Я вважаю це напівправдою. Хаскелл має дивовижну здатність абстрагуватися, і це включає абстракцію над імперативними ідеями. Наприклад, у 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
Він не може друкувати на консолі, встановлювати мережеві підключення тощо. У поєднанні з можливістю абстрагування ви можете писати функції, які діють на "будь-яке обчислення, яке створює потік" тощо.
Насправді все, що стосується абстракційних здібностей Хаскелла, робить його дуже тонкою імперативною мовою.
Однак помилкова половина - це синтаксис. Я вважаю Хаскелла досить багатослівним і незручним для використання в імперативному стилі. Ось приклад обчислення в імперативному стилі з використанням наведеного вище 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.
На закінчення, із синтаксичним підтяжкою обличчя Хаскелл цілком може бути найкращою імперативною мовою. Але, за характером підтяжки обличчя, це було б заміною чогось внутрішнього красивого та справжнього чимось зовні гарним та підробленим.
EDIT : Контраст 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
Для чого це варте, ось як виглядає чиста заміна в 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"
(.)
Оператор функціональної композиції .