Section 3 - Examples : Base Type and Structures

Back ] Home ] Up ] Next ]

Section 3 - Examples : Base Type and Structures

Now we look at several examples of observe being used.


Section 3.1 Observing a finite list

As an example consider:

main = runO ex1
ex1 :: IO ()
ex1 = print 
       ((observe "list" :: Observing [Int]) [0..9])

If we this run program, we would make the observation

-- list
  0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : []

We have successfully observed an intermediate data structure, without changing the value or semantics of the final Haskell program!

We use the observe type synonym to allow us to be explicit about what type we think we are observing.

type Observing a = a -> a

However, using this explicit typing is optional, we could have equally well written

ex1 :: IO ()
ex1 = print (observe "list" [0..9])

This definition however relies on the default mechanism choosing an Int or Integer list. Typically the type of observe is fully determined by its context, but we sometimes include the type signature with our examples to make explicit to the reader what type is being observed.


Section 3.2 Observing a intermediate list

Observe can be used partially applied, which is the typical use scenario when observing inside a point-free pipeline. 

main = runO ex2
ex2 :: IO ()
ex2 = print 
    . reverse
    . (observe "intermediate" :: Observing [Int])
    . reverse
    $ [0..9]

This observe makes the following observation 

-- intermediate
  9 : 8 : 7 : 6 : 5 : 4 : 3 : 2 : 1 : 0 : []

Section 3.3 Observing a infinite list

Both the lists we have observed so far were finite. As an example of an observation on an infinite list, consider:

main = runO ex3
ex3 :: IO ()
ex3 = print 
       (take 10
          (observe "infinite list" ([0..] :: [Int]))
       )

Here we observe an infinite list, starting at 0, which has the first 10 elements taken from it, and printed. Running this example allows us to make the observation

-- infinite list
  0 : 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : _

We can see that 0 to 9 have been evaluated, but the tail of the 10th cons has not been evaluated, rendered using the notation "_". If more of the list were extracted, we would see more cons cells, etc. 


3.4 Observing lists with unevaluated elements

So what about unevaluated elements of the list? What if we to take the length of a finite list?

main = runO ex4

ex4 :: IO ()
ex4 = print 
       (length
         (observe "finite list" ([1..10] :: [Int]))
       )

This gives the observation as

-- finite list
  _ : _ : _ : _ : _ : _ : _ : _ : _ : _ : []

What if the elements were bottom?

main = runO ex5
ex5 :: IO ()
ex5 = print 
       (length
         ((observe "finite list" :: Observe [()])
            [ error "oops!" | _ <- ([0..9] :: [Int])]
         )
       )

This gives exactly the same debugging output as ex4. Because we never evaluate the elements, it did not matter what they were, even if the elements were bottom. We needed to give them some non-polymorphic type, so we can actually observe them, though. So, what about if only some elements are observed?

main = runO ex6
ex6 :: IO ()
ex6 = let xs = observe "list" ([0..9] :: [Int])
      in print (xs !! 2 + xs !! 4)

This example gives

-- list
  _ : _ : 2 :  _ : 4 : _

We can use observe to both see data inside intermediate structures, and also as a tool to see how much of a lazy structure is actually evaluated, without fear of changing the evaluation order. This is where the power of observe lies.


Section 3.5 Using more than one observe at a time

One program can contain many specific instances of observe. We might rewrite the natural example from the introduction.

main = runO (print (ex8 3408))
ex8 :: Int -> [Int]
ex8
 = (observe "after reverse" :: Observing [Int])
 . reverse 
 . (observe "after map (`mod` 10)" :: Observing [Int])
 . map (`mod` 10)
 . (observe "after takeWhile (/= 0)" :: Observing [Int])
 . takeWhile (/= 0) 
 . (observe "after iterate (`div` 10)" :: Observing [Int])
 . iterate (`div` 10)
Running this on the example data, 3408, gives:

-- after iterate (`div` 10)
3408 : 340 : 34 : 3 : 0 : _
-- after map (`mod` 10)
8 : 0 : 4 : 3 : []
-- after reverse
3 : 4 : 0 : 8 : []
-- after takeWhile (/= 0)
3408 : 340 : 34 : 3 : [] 

Back ] Home ] Up ] Next ]