Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I would also like to add that laziness adds a lot of power and flexibility to common procedures.

For example, let's look at a canonical quicksort in Haskell:

  quicksort :: Ord a => [a] -> [a]

  quicksort []     = []

  quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater)

      where

          lesser  = filter (< p) xs

          greater = filter (>= p) xs
This is nothing too interesting, but now let's say you want to write a function to find the minimum value of a list

In Python, this would probably be done like this:

  def min_list(lst):

      current_minimum = lst[0]

      for e in lst[1:]:

          if e < current_minimum:

              current_minimum = e

      return current_minimum
However, in Haskell, a very similar procedure can be defined like this:

  minimum xs = head (quicksort xs)


(head returns the first element of a list)

Now, if have the slightest care for efficiency, you would never, ever write a function like this in a strict language.

However, in Haskell, minimum [5,2,7,1,3] would be evaluated like this:

  > minimum [5,2,7,1,3]

  > head (quicksort [5,2,7,1,3])
Now, head 'demands' one element from quicksort, so quicksort would start getting evaluated

  > head (lesser ++ ...)  [p = 5]
(++) is nonstrict, and only evaluates the elements you really need. Since right now only one element is demanded, we can expect that nothing to the right of (++) would be evaluated

  > head ((quicksort (filter (<5) [2,7,1,3]) ) ++ ...)
Now filter is also lazy, and it will only evaluate elements as needed. Right now, only one element is demanded by quicksort(notice the (p:xs) in the definition. quicksort only cares about the 'p' right now)

  > head ((quicksort (2:(filter (<5) [7,1,3])) ++ ...)

  > head ((quicksort (filter (<2) 

                              xs) ++ ... ) ++ ...)                                     [p = 2, xs = filter (<5) [7,1,3]]

Now, quicksort demands another element from filter (<2) ..., so filter (<5) runs until it finds the first element that is also (<2) > head ((quicksort (1:(filter (<2) (filter (<5) [3] ++ ...)) ) ++ ...) ++ ...)

Filter will run through the entire list, comparing each element to 5,2 and 1 in order, and find nothing, returning [], which will hit the base case for quicksort []= []

  > head ( [] ++ [1] ++ ...)

Simplifying to

  > head ([1] ++ ...)
(++) has enough evaluated elements to give '1' to head, which finally returns

  > 1

Notice that we passed through the list only once, just like in the python, in contrast to running an entire quicksort. While this is not as efficient as the python version as it keeps a few unnessary elements in memory, and compares elements to all previously found minimums, it is much more efficient than evaluating quicksort, and is pretty damn cool. With some GHC magic, the memory usage is also reduced somewhat.

I personally believe that the expressiveness gained through this procedure(we wrote quicksort and gained minimum for free!) offsets the loss in efficiency.



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: