Why is Haskell (sometimes) called the "Best Imperative Language"?

(I hope this question is about the topic - I tried to find the answer, but did not find the final answer. If this happens off-topic or has already been answered, please die / delete it.)

I remember hearing / reading a half-noisy comment that Haskell was the best imperative language several times, which of course sounds weird because Haskell is usually best known for its functionality.

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

+72
imperative-programming haskell
Jul 08 2018-11-11T00:
source share
3 answers

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 .

+77
Jul 08 2018-11-11T00:
source share

This is not a joke, and I believe in it. I will try to make this available to those who do not know Haskell. Haskell uses do-notation (by the way) so you can write imperative code (yes, it uses monads, but don't worry about that). Here are some of the benefits that Haskell gives you:

  • Simple creation of routines. Let's say that I want the function to print the value for stdout and stderr. I can write the following, defining a routine in one short line:

     do let printBoth s = putStrLn s >> hPutStrLn stderr s printBoth "Hello" -- Some other code printBoth "Goodbye" 
  • Simple code. Given what I wrote above, if now I want to use the printBoth function to print the entire list of lines, which is easy to do by passing my routine to the mapM_ function:

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

    Another example, although not necessarily, sorts. Let's say you want to sort the rows only by length. You can write:

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

    Which will give you ["b", "cc", "aaaa"]. (You can write it shorter than this, too, but no matter at the moment.)

  • Simple code reuse. This mapM_ function mapM_ used a lot and replaces each cycle in other languages. There is also a forever that acts like some time (true) and various other functions that can be passed by the code and execute it differently. Thus, loops in other languages ​​are replaced by these control functions in Haskell (which are not special - you can easily define them yourself). In general, this makes it difficult to get the loop condition incorrectly, just as for every loop, it is harder to make mistakes than the equivalents of a long iterator (for example, in Java) or array indexing loops (for example, in C).

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

     do line <- getLine putStrLn (foo line) 

    I immediately realized that foo has no unexpected side effects (such as updating a global variable or freeing memory or something else), because this type must be String → String, which means it is a pure function; no matter what value I pass, it should return the same result every time, without side effects. Haskell perfectly separates the side code from the clean code. In something like C or even Java, this is not obvious (is this a property of the change to the getFoo () method? I hope not, but it can happen ...).

  • Garbage collection. These days, many languages ​​are garbage, but it is worth mentioning: no problems with allocating and freeing memory.

There are probably a few more benefits, but these are the ones that come to mind.

+21
Jul 08 '11 at 10:01
source share

In addition to what was mentioned earlier, sometimes side effects are of paramount importance. Here is a silly example to show an idea:

 f = sequence_ (reverse [print 1, print 2, print 3]) 

This example shows how you can create calculations with side effects ( print in this example), and then put them into data structures or manipulate them in other ways before executing them.

+15
Jul 08 '11 at 11:20
source share



All Articles