[PRL] referential transparency

Dave Herman dherman at ccs.neu.edu
Tue Aug 31 12:02:25 EDT 2004


>>So Scheme is not r.t. because you can't replace the following
>>expression....
> 
> Right.

I wish Matthias were around, because I remember several months ago he 
was protesting the claim that Haskell is r.t. and Scheme isn't, claiming 
(I think) that they didn't have a proper definition for r.t. I wonder 
what he had in mind.

> If you can treat your program as a functional transducer then you can
> treat your input as a lazy sequence of values.  Since each term in the
> sequence never changes, you can freely substitute the value of the
> term wherever a reference to the term appears *provided* that you
> continue to enforce the constraint that a term is never `forced' until
> the one immediately preceeding it has been forced.

Okay, so here's a simple example:

-- BEGIN PROGRAM

test :: Int -> IO Bool
test = \i -> do c <- hGetChar stdin
                 return ((c == 'q') && (i < 10))

main :: IO ()
do b <- test 5
    if b then (putStrLn "foo") else (putStrLn "bar")

-- END PROGRAM

The definition of `test' looks like it's not r.t., because given the 
same integer, it might give a different boolean answer based on what it 
reads from stdin. However, since it translates to a "world transformer," 
it's got a functional definition. Here's an unfolding of the definitions:

-- BEGIN COMPILATION

hGetChar :: Handle -> World -> (World, Char)
test :: Int -> World -> (World, Bool)
main :: World -> (World, ())

test = \i -> bind (hGetChar stdin)
                   \c -> return ((c == 'q') && (i < 10))
      {- def'n hGetChar, return -}
      = \i -> bind \world -> $PRIM.hGetChar world stdin
                   \c -> \world -> (world, ((c == 'q') && (i < 10)))
      {- def'n bind -}
      = \i -> \w1 -> let (w2, ch) = $PRIM.hGetChar w1 stdin
                     in (((\c -> \w3 -> (w3, ((c == 'q') && (i < 10))))
                          ch) w2)
      {- beta-substitution -}
      = \i -> \w1 -> let (w2, ch) = $PRIM.hGetChar w1 stdin
                     in (w2, ((ch == 'q') && (i < 10)))

main = bind (test 5)
             \b -> if b then (putStrLn "foo") else (putStrLn "bar")
      {- def'n bind -}
      = \w1 -> let (w2, b) = ((test 5) w1)
               in (if b then (putStrLn "foo") else (putStrLn "bar"))
      {- def'n test -}
      = \w1 -> let (w2, b) = let (w3, ch) = ($PRIM.hGetChar w1 stdin)
                             in (w3, ((ch == 'q') && (i < 10)))
                   in (if b then ($PRIM.putStrLn "foo" w2, ())
                            else ($PRIM.putStrLn "bar" w2, ()))
      {- let-rotation -}
      = \w1 -> let (w3, ch) = $PRIM.hGetChar w1 stdin
               in let (w2, b) = (w3, ((ch == 'q') && (i < 10)))
                  in (if b then ($PRIM.putStrLn "foo" w2, ())
                           else ($PRIM.putStrLn "bar" w2, ()))

-- END COMPILATION

In the end, the program translates to a world transformer where every 
subexpression can be replaced with the value it converges to, but since 
all IO-related expressions rely on the world that results from the 
previous IO-related expression, that world has to have been computed 
before we can know what that value is.

Which is all just a long-winded way of saying what Carl and Joe just said.

Dave




More information about the PRL mailing list