10

One of the highly-touted features is that if a program compiles, it highly likely to be mostly correct, more so than a program written in a language with a less sophisticated or strict type system.

That is, Haskell is a system for translating runtime errors to compiler errors :-)

I wonder, does programming in Haskell give rise to situations where a programmer could introduce a runtime bug that doesn't have an obvious analog (in appearance and frequency) in a less strongly-typed language?

Some basic examples that pop into my head: (not great, I am looking for advice on what to be wary of)

  • asymptotic performance bugs due to laziness
  • infinite loop due to wrongly structured recursion
  • fundeps/type-families pushing logic to type level, where code is more "arcane" and errors are harder to spot?

Other/better examples of gotchas?

misterbee
  • 5,142
  • 3
  • 25
  • 34
  • 3
    the first two are definitely not Haskell-specific... – flq Jan 13 '14 at 17:03
  • 2
    I'd say that "black hole" errors are particular to Haskell (and other lazy languages). They can happen when you have recursion not involving functions. – augustss Jan 13 '14 at 17:07
  • @flq, Haskell encourages use of recursion to avoid mutable state, so Haskell programs tend to have more recursion than other language programs, so recursion bugs can be (a) more widespread threat, yet (b) less likely due to quickly-developed programmer familiarity :-| – misterbee Jan 13 '14 at 17:17
  • 6
    I'd rather say Haskell encourages the use of _higher-order-functions_ to avoid both mutable state and recursion. – leftaroundabout Jan 13 '14 at 17:27
  • 1
    This is an interesting question that calls for facts, not opinions. The close votes are inappropriate. – Greg Bacon Jan 13 '14 at 18:07

3 Answers3

8

It doesn’t have to be asymptotic, but space leaks due to lazyness are a problem in real-world applications of Haskell. I know of Haskell-using companies that completely switched to strict datatypes (while still using the laziness of function parameters).

For sources on that view, see:

  • E. Hesselink. Silk: making the sematic web functional. Functional Programming Exchange 2012, London, March 2012.
  • C. J. Sampson. Experience report: Haskell in the ’real world’: writing a commercial application in a lazy functional lanuage. In ICFP ’09, pages 185–190, 2009.
  • S. Wehr. Kommerzielle Softwareentwicklung mit Haskell. Hal6, Leipzig, Oct 2011. Slides (in German).
Joachim Breitner
  • 25,395
  • 6
  • 78
  • 139
  • 1
    What are some "Haskell-using" companies... I need to apply there! – poy Jan 13 '14 at 17:26
  • So "all strict" data types means no infinite lists... or that infinite structures are always explicitly wrapped in a function? Data constructors *are* functions, so the boundary isn't obvious. Or, maybe you mean that all simple data types are strict, as in `Pair !Int !Int` or (showing the distinction I am drawing:) `data IList = Nil | Cons !Int (IList )` – misterbee Jan 13 '14 at 17:29
  • 1
    @Andrew You know the [Haskell tag page](http://stackoverflow.com/questions/tagged/haskell) has a [Haskell jobs](http://careers.stackoverflow.com/jobs/tag/haskell) link, no? – not my job Jan 13 '14 at 17:30
  • 1
    @misterbee: I believe they really use strict lists, i.e. no infinite lists. After all, in practice you only need them for `zip [0..]` :-) – Joachim Breitner Jan 13 '14 at 17:40
  • 2
    Standard Chartered's Mu is an example of using Haskell strictly: http://anil.recoil.org/papers/2011-cufp-scribe-preprint.pdf – Ganesh Sittampalam Jan 13 '14 at 17:56
  • @chunksOf50 I never thought to look! – poy Jan 13 '14 at 18:34
  • 3
    @GaneshSittampalam: Money quote from that PDF: "The chief downside of strict semantics, in their experience, is the increased difficulty of modular composition." Indeed. – misterbee Jan 13 '14 at 20:07
4

Laziness, especially lazy I/O where pure functions can force IO actions or closing a file Handle prior to reading. The Haskell course by Stanford University has good information on this in the Iteratee lecture. Imho, this lecture series is very well written and covers a lot of ground.

Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
Akaberto
  • 433
  • 3
  • 10
  • Can you give an example of closing a file handle before reading that relies on Haskell (i.e. doesn't immediately translate into, say, C)? – Daniel Wagner Jan 13 '14 at 18:22
  • [source](http://www.haskell.org/haskellwiki/Iteratee_I/O) `wrong = do fileData <- withFile "test.txt" ReadMode hGetContents` `putStr fileData` From a strict, imperative languages background, you'd expect this wouldn't be a problem, but it isn't. `withFile` kills the handle before anything is read, cause it is lazy. The putStr prints junk as nothing was read. – Akaberto Jan 13 '14 at 18:38
  • 5
    @DanielWagner: How about `import System.IO; withFile "/dev/random" ReadMode hGetContents >>= putStr`, which, instead of streaming noise, doesn't print anything? Lazy IO means that the reading happens later than one might immediately think it would, so it's not immediately obvious that you're closing the handle before reading from it. – Antal Spector-Zabusky Jan 13 '14 at 18:43
  • Akaberto, is that a line break (and indent) before putStr? Otherwise it doesn't compile. With a linebreal In GHCi, I get no output (same as @AntalS-Z's example), not junk. Junk seems even scarier! – misterbee Jan 13 '14 at 20:04
  • 1
    @misterbee: Yes, there is a line break. And yes, I made a mistake. There is no output. My bad. Antal S-Z has got it right. As a side note, `do` is just syntactic sugar for `>>=`. The [haskell wikiBook chapter on do notation](http://en.wikibooks.org/wiki/Haskell/do_Notation) explains it rather well. – Akaberto Jan 13 '14 at 20:15
1

Consider the following dataype:

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}

import Data.Foldable
import qualified Data.Traversable as T
import Control.Monad.Random

data U a = U [a] a deriving (Show,Functor,Foldable,T.Traversable)

I want to create a U Int with random values. It's easy using the Traversable instance:

ri :: Rand StdGen Int
ri = getRandomR (0,3)

randomU :: U Int
randomU =  flip evalRand (mkStdGen 7) 
         . T.sequence
         $ U (replicate 3 ri) ri  

putStrLn . show $ randomU -- works

Now I create a random infinite U Int and print the first three values of the list:

randomInfiniteU :: U Int
randomInfiniteU =  flip evalRand (mkStdGen 7) 
                 . T.sequence
                 $ U (repeat ri) ri  

putStrLn . show $ take 3 $ (\(U l _) -> l) $ randomInfiniteU

Works fine as well. As a last test, let's print the single value to the right:

putStrLn . show $ (\(U _ i) -> i) $ randomInfiniteU

Ugh. This hangs.

danidiaz
  • 26,936
  • 4
  • 45
  • 95
  • 2
    It hangs because the infinite value is sequenced before the finite value? (Which we didn't *desire*, but accidentally coded in. Changing the data type to `data U a = U a [a]` fixes the bug, but is brittle. We need a `T.Traversable` instance that traverses the finite portion first, instead of the generic version. And a way to warn/lint on the situation would be nice!) Reminds of Oleg's argument against associative MonadPlus; sometimes you don't want the "help" of right-association. http://stackoverflow.com/questions/15722906/must-mplus-always-be-associative-haskell-wiki-vs-oleg-kiselyov – misterbee Jan 13 '14 at 20:16