Ich halte es für eine halbe Wahrheit. Haskell hat eine erstaunliche Fähigkeit zu abstrahieren, und dazu gehört auch die Abstraktion über imperative Ideen. Zum Beispiel hat Haskell keine eingebaute imperative while-Schleife, aber wir können sie einfach schreiben und jetzt tut es:
while :: (Monad m) => m Bool -> m () -> m ()
while cond action = do
c <- cond
if c
then action >> while cond action
else return ()
Diese Abstraktionsebene ist für viele imperative Sprachen schwierig. Dies kann in imperativen Sprachen erfolgen, die Verschlüsse haben; z.B. Python und C #.
Haskell hat aber auch die (höchst einzigartige) Fähigkeit, zulässige Nebenwirkungen zu charakterisieren mithilfe der Monad-Klassen . Zum Beispiel, wenn wir eine Funktion haben:
foo :: (MonadWriter [String] m) => m Int
Dies kann eine "zwingende" Funktion sein, aber wir wissen, dass sie nur zwei Dinge tun kann:
- Einen Strom von Strings "ausgeben"
- Rückgabe eines Int
Es kann nicht auf der Konsole gedruckt oder Netzwerkverbindungen usw. hergestellt werden. In Kombination mit der Abstraktionsfunktion können Sie Funktionen schreiben, die auf "jede Berechnung, die einen Stream erzeugt" usw. wirken.
Es geht wirklich nur um Haskells Abstraktionsfähigkeiten, die es zu einer sehr guten imperativen Sprache machen.
Die falsche Hälfte ist jedoch die Syntax. Ich finde Haskell ziemlich ausführlich und umständlich in einem imperativen Stil zu verwenden. Hier ist ein Beispiel für eine Berechnung im imperativen Stil unter Verwendung der obigen while
Schleife, die das letzte Element einer verknüpften Liste findet:
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
All dieser IORef-Müll, das doppelte Lesen, das Ergebnis eines Lesevorgangs binden zu müssen, fmapping ( <$>
), um das Ergebnis einer Inline-Berechnung zu verarbeiten ... alles sieht nur sehr kompliziert aus. Es macht sehr viel Sinn von einem funktionalen Sicht ist dies , aber zwingende Sprachen neigen dazu, die meisten dieser Details unter den Teppich zu kehren, um ihre Verwendung zu vereinfachen.
Zugegeben, vielleicht, wenn wir einen anderen verwendet haben while
wäre es sauberer Kombinator Stils verwenden würden. Wenn Sie diese Philosophie jedoch weit genug bringen (indem Sie eine Vielzahl von Kombinatoren verwenden, um sich klar auszudrücken), gelangen Sie wieder zur funktionalen Programmierung. Haskell im imperativen Stil "fließt" einfach nicht wie eine gut gestaltete imperative Sprache, z. B. Python.
Zusammenfassend könnte Haskell mit einem syntaktischen Facelifting die beste imperative Sprache sein. Aber aufgrund der Natur von Facelifts würde es etwas innerlich Schönes und Reales durch etwas äußerlich Schönes und Falsches ersetzen.
EDIT : Kontrast lastElt
zu dieser Python-Transliteration:
def last_elt(xs):
assert xs, "Empty list!!"
lst = xs
ret = xs.head
while lst:
ret = lst.head
lst = lst.tail
return ret
Gleiche Anzahl von Zeilen, aber jede Zeile hat einiges weniger Rauschen.
BEARBEITEN 2
So sieht ein reiner Ersatz in Haskell aus:
lastElt = return . last
Das ist es. Oder wenn Sie mir verbieten, Folgendes zu verwenden Prelude.last
:
lastElt [] = fail "Unsafe lastElt called on empty list"
lastElt [x] = return x
lastElt (_:xs) = lastElt xs
Oder, wenn Sie es an die Arbeit auf jedem wollen Foldable
Datenstruktur und erkennen , dass Sie nicht wirklich brauchen IO
, um Griff - Fehler:
import Data.Foldable (Foldable, foldMap)
import Data.Monoid (Monoid(..), Last(..))
lastElt :: (Foldable t) => t a -> Maybe a
lastElt = getLast . foldMap (Last . Just)
mit Map
zum Beispiel:
λ➔ let example = fromList [(10, "spam"), (50, "eggs"), (20, "ham")] :: Map Int String
λ➔ lastElt example
Just "eggs"
Der (.)
Operator ist die Funktionszusammensetzung .