[Agda] IO, semi-closed handles, and problems with extensional equality

Alan Jeffrey ajeffrey at bell-labs.com
Wed Sep 22 04:30:24 CEST 2010


Hi everyone,

Starting a new thread, since we've stopped talking about FFIs a while 
ago.  Old thread is here:

   http://article.gmane.org/gmane.comp.lang.agda/1936

I kicked the hornets nest and asked why IO is based on Costring rather 
than String.  It seems to me that the culprit is semi-closed handles in 
Haskell, as described here:

   http://www.haskell.org/onlinelibrary/io.html#sect21.2.2

"[After a call to hGetContents hdl], hdl is effectively closed, but 
items are read from hdl on demand and accumulated in a special list 
returned by hGetContents hdl."

AFAICT, this is the underlying source of laziness in the IO library, the 
other functions return results strictly, for example there's no problem 
with hGetLine having the Agda type (Handle -> IO String) as it waits 
until a NL before returning a fully evaluated string.

The discussion around IO and lazy strings made me realize there's a 
question as to what the goals for the IO library should be.  I will 
posit an innocent-looking goal, and then give a counterexample using the 
current IO bindings :-)

   GOAL:
   Extensionally equivalent programs have the same runtime behaviour.

Obviously this is an informal goal, as the Haskell runtime system isn't 
formalized, but at least we can do experiments.  For example, here are a 
couple of programs which do not have the same runtime behaviour:

-- This program blocks until it receives input

hello₁ : Costring → IO Unit
hello₁ []       = putStrLn (fromString "hello")
hello₁ (x ∷ xs) = putStrLn (fromString "hello")

program₁ : IO Unit
program₁ = getContents >>= hello₁

-- This program prints immediately

hello₂ : Costring → IO Unit
hello₂ x = putStrLn (fromString "hello")

program₂ : IO Unit
program₂ = getContents >>= hello₂

For extensional equivalence, I should probably define a proper 
equivalence on IO, which includes the monad laws, but for right now I'll 
do a hack and extend propositional equivalence with a postulate 
expressing that >>= is a congruence (note, wrt extensional equivalence 
on the right):

   postulate _⟨>>=⟩_ :
     {A B : Set} → {io₁ io₂ : IO A} → {f₁ f₂ : A → IO B} →
       (io₁ ≡ io₂) → (∀ a → f₁ a ≡ f₂ a) →
         (io₁ >>= f₁) ≡ (io₂ >>= f₂)

Unfortunately, it is easy to show that (program₁ ≡ program₂), thus 
giving a counterexample to the goal.  (The proof uses the snapshot 2.2.7 
compiler, but can probably be munged to go through in 2.2.6).

Possible fixes:

a) Abandon the goal (er, so what's the point of mechanized proof then?)

b) Abandon semi-closed handles (ouch, buffering inefficencies!)

c) Er, something really clever I've not thought of.

Thoughts?

A.
-------------- next part --------------
open import IO.Primitive
open import Data.Char
open import Relation.Binary.PropositionalEquality
open import Foreign.Haskell

module IOvsEquiv where 

Costring = Colist Char

-- Cheating for now and making extensionality of >>= an axiom.
-- Should we use a separate extensional equivalence for IO?

postulate _⟨>>=⟩_ : {A B : Set} → {io₁ io₂ : IO A} → {f₁ f₂ : A → IO B} → (io₁ ≡ io₂) → (∀ a → f₁ a ≡ f₂ a) → (io₁ >>= f₁) ≡ (io₂ >>= f₂)

-- This program blocks until it receives input

hello₁ : Costring → IO Unit
hello₁ []       = putStrLn (fromString "hello")
hello₁ (x ∷ xs) = putStrLn (fromString "hello")

program₁ : IO Unit
program₁ = getContents >>= hello₁

-- This program prints immediately

hello₂ : Costring → IO Unit
hello₂ x = putStrLn (fromString "hello")

program₂ : IO Unit
program₂ = getContents >>= hello₂

-- Testy!

main = program₁
-- main = program₂

-- Proofy! (Checked w/ Agda 2.2.7)

ohdear : program₁ ≡ program₂
ohdear = refl ⟨>>=⟩ lemma where
  lemma : ∀ a → hello₁ a ≡ hello₂ a
  lemma [] = refl
  lemma (x ∷ xs) = refl

-- So we have two programs with quite different behaviour,
-- but which have been proved extensionally equivalent.
-- Oh dear.


More information about the Agda mailing list