I think this is half true. Haskell has an amazing ability to abstract, and this includes an abstraction of peremptory ideas. For example, Haskell does not have a built-in imperative while loop, but we can just write it, and now it does:
while :: (Monad m) => m Bool -> m () -> m () while cond action = do c <- cond if c then action >> while cond action else return ()
This level of abstraction is difficult for many imperative languages. This can be done in imperative languages ​​with closure; eg. Python and C #.
But Haskell also has a (very unique) ability to characterize acceptable side effects using Monad classes. For example, if we have a function:
foo :: (MonadWriter [String] m) => m Int
This may be an “imperative” function, but we know that it can only do two things:
- "Output" stream of lines
- return int
It cannot print on the console or establish network connections, etc. In combination with the possibility of abstraction, you can write functions that act on “any calculations that create a stream”, etc.
It's really all about Haskell's abstraction abilities that make him a very subtle imperative language.
However, the false half is the syntax. I find Haskell quite verbose and inconvenient to use in an imperative style. The following is an example of an imperative-style calculation using the above while , which finds the last item in a linked list:
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 that IORef garbage, double reading, you need to bind the reading result, fmapping ( <$> ) to work with the result of the built-in calculation ... it all looks very complicated. This makes a lot of sense in terms of functionality, but imperative languages ​​tend to cover most of these details under the mat to make them easier to use.
Admittedly, perhaps if we used a different while style combinator, it would be cleaner. But if you solve this philosophy enough (using a rich set of combinators to express yourself clearly), you will get functional programming again. Haskell’s imperial style simply doesn’t “flow” like a well-designed imperative language, for example. python.
In conclusion, with a syntactic facelift, Haskell could very well be the best imperative language. But, by the nature of a facelift, it would replace something inwardly beautiful and present with something outwardly beautiful and fake.
EDIT : Contrast lastElt with this 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
The same number of lines, but each line has a bit less noise.
EDIT 2
For what it's worth, it looks like a clean replacement in Haskell:
lastElt = return . last
What is it. Or, if you forbid me to use Prelude.last :
lastElt [] = fail "Unsafe lastElt called on empty list" lastElt [x] = return x lastElt (_:xs) = lastElt xs
Or, if you want him to work on any Foldable structure and acknowledge that you really don't need IO to handle errors:
import Data.Foldable (Foldable, foldMap) import Data.Monoid (Monoid(..), Last(..)) lastElt :: (Foldable t) => ta -> Maybe a lastElt = getLast . foldMap (Last . Just)
with Map , for example:
λ➔ let example = fromList [(10, "spam"), (50, "eggs"), (20, "ham")] :: Map Int String λ➔ lastElt example Just "eggs"
Operator (.) Composition of functions .