Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Haskell (sometimes) referred to as "Best Imperative Language"?

(I hope this question is on-topic -- I tried searching for an answer but didn't find a definitive answer. If this happens to be off-topic or already answered, please moderate/remove it.)

I remember having heard/read the half-joking comment about Haskell being the best imperative language a few times, which of course sounds weird as Haskell is usually best known for its functional features.

So my question is, what qualities/features (if any) of Haskell give reason to justify Haskell being deemed the best imperative language -- or is it actually more of a joke?

like image 319
hvr Avatar asked Jul 08 '11 09:07

hvr


People also ask

Which language is imperative language?

These are the best-known imperative programming languages:Fortran. Java. Pascal. ALGOL.

What is an imperative high level language?

Imperative languages. In this paradigm, programmers will solve a problem by writing a set of instructions that state how a problem should be solved.

What is imperative programming used for?

Imperative programming is a software development paradigm where functions are implicitly coded in every step required to solve a problem. In imperative programming, every operation is coded and the code itself specifies how the problem is to be solved, which means that pre-coded models are not called on.

Why is C++ imperative?

Programs written in imperative languages (like Java and C++) consist of a sequence of statements, where each statement is an instruction that causes the computer to do one operation. Statements, and therefore programs, are composed of a pattern of keywords, symbols, and programmer-named entities.


2 Answers

I consider it a half-truth. Haskell has an amazing ability to abstract, and that includes abstraction over imperative ideas. For example, Haskell has no 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 that have closures; eg. Python and C#.

But Haskell also has the (highly unique) ability to characterize allowed side-effects, using the Monad classes. For example, if we have a function:

foo :: (MonadWriter [String] m) => m Int 

This can be an "imperative" function, but we know that it can only do two things:

  • "Output" a stream of strings
  • return an Int

It can't print to the console or establish network connections, etc. Combined with the abstraction ability, you can write functions which act on "any computation that produces a stream", etc.

It's really all about Haskell's abstraction abilities that makes it a very fine imperative language.

However, the false half is the syntax. I find Haskell pretty verbose and awkward to use in an imperative style. Here is an example imperative-style computation using the above while loop, which finds the last element of 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, the double read, having to bind the result of a read, fmapping (<$>) to operate on the result of an inline computation... it's all just very complicated looking. It makes a whole lot of sense from a functional perspective, but imperative languages tend to sweep most of these details under the rug to make them easier to use.

Admittedly, perhaps if we used a different while-style combinator it would be cleaner. But if you take that philosophy far enough (using a rich set of combinators to express yourself clearly), then you arrive at functional programming again. Imperative-style Haskell just doesn't "flow" like a well-designed imperative language, e.g. python.

In conclusion, with a syntactic face-lift, Haskell might well be the best imperative language. But, by the nature of face lifts, it would be replacing something internally beautiful and real with something externally 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  

Same number of lines, but each line has quite a bit less noise.


EDIT 2

For what it's worth, this is how a pure replacement in Haskell looks like:

lastElt = return . last 

That's it. Or, if you forbid me from using Prelude.last:

lastElt [] = fail "Unsafe lastElt called on empty list" lastElt [x] = return x lastElt (_:xs) = lastElt xs 

Or, if you want it to work on any Foldable data structure and recognize that you don't actually need IO to handle errors:

import Data.Foldable (Foldable, foldMap) import Data.Monoid (Monoid(..), Last(..))  lastElt :: (Foldable t) => t a -> 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" 

The (.) operator is function composition.

like image 81
luqui Avatar answered Sep 22 '22 18:09

luqui


It's not a joke, and I believe it. I'll try to keep this accessible for those who don't know any Haskell. Haskell uses do-notation (among other things) to allow you to write imperative code (yes, it uses monads, but don't worry about that). Here's some of the advantages that Haskell gives you:

  • Easy creation of subroutines. Let's say that I want a function to print a value to stdout and stderr. I can write the following, defining the subroutine with one short line:

    do let printBoth s = putStrLn s >> hPutStrLn stderr s    printBoth "Hello"    -- Some other code    printBoth "Goodbye" 
  • Easy to pass code around. Given that I've written the above, if I now want to use the printBoth function to print out all of a list of strings, that's easily done by passing my subroutine to the mapM_ function:

    mapM_ printBoth ["Hello", "World!"] 

    Another example, although not imperative, is sorting. Let's say you want to sort strings solely by length. You can write:

    sortBy (\a b -> compare (length a) (length b)) ["aaaa", "b", "cc"] 

    Which will give you ["b", "cc", "aaaa"]. (You can write it shorter than that, too, but never mind for now.)

  • Easy to re-use code. That mapM_ function is used a lot, and replaces for-each loops in other languages. There's also forever which acts like a while (true), and various other functions that can be passed code and execute it in different ways. So loops in other languages are replaced by these control functions in Haskell (which are not special -- you can define them yourself very easily). In general this makes it hard to get the loop condition wrong, just like for-each loops are harder to get wrong than the long-hand iterator equivalents (e.g. in Java), or array-indexing loops (e.g. in C).

  • Binding not assignment. Basically, you can only assign to a variable once (rather like single static assignment). This removes a lot of confusion about the possible values of a variable at any given point (its value is only set on one line).
  • Contained side effects. Let's say that I want to read a line from stdin, and write it on stdout after applying some function to it (we'll call it foo). You can write:

    do line <- getLine    putStrLn (foo line) 

    I know immediately that foo doesn't have any unexpected side effects (like updating a global variable, or deallocating memory, or whatever), because it's type must be String -> String, which means it is a pure function; whatever value I pass, it must return the same result every time, without side effects. Haskell nicely separates the side-effecting code from the pure code. In something like C, or even Java, this is not obvious (does that getFoo() method change state? You'd hope not, but it might do...).

  • Garbage collection. A lot of languages are garbage collected these days, but worth mentioning: no hassles of allocating and deallocating memory.

There's probably a few more advantages besides, but those are the ones that come to mind.

like image 39
Neil Brown Avatar answered Sep 26 '22 18:09

Neil Brown