- Recent Changes 新聞
- History 歷史
- Preferences 喜好
- Discussion 討論
Martín Escardó
writes
a seemingly impossible functional program: for any
total function f
from infinite bit sequences
to Bool
, his total function find
maps f
to an infinite bit sequence, such that if
f
returns True
on any sequence then
find f
is the least such sequence. It’s a good read
and I recommend it. (Update:
he wrote a follow-up article that defines a monad of searchable
sets.)
Essentially find
performs an exhaustive search by
depth-first backtracking. It’s impressive that find
can be programmed totally and without inspecting the source code of
its argument f
, even though there are an uncountable
number of infinite bit sequences. Escardó makes find
go faster using lazy evaluation in Haskell: his
find_iii
uses an infinite sequence of lazily evaluated
bits. But it wasn’t clear to me what role lazy evaluation plays in
find_vii
, which is his final, fastest version of
find
.
Contrary to
what some people inexplicably seem to think, I don’t know from
category theory, and I haven’t read Escardó’s paper or
code at
LICS 2007. Still, I suspect the/an essence of find_vii
is to replace an infinite sequence of lazily evaluated bits by a
lazily evaluated binary tree of bits. To confirm this hypothesis, I
reimplemented his algorithm as follows. You can download
this post as a program.
module Exhaustive where
We represent a function from natural numbers to some type
a
by an infinite binary tree whose nodes are labeled
by a
. To be more precise: the root node stores what
the function returns at 0; the left branch stores the return values
at positive odd integers, and the right branch stores the return
values at positive even integers.
data T a = T a (T a) (T a)
Of course, the container T
is a functor.
instance Functor T where fmap f (T z o e) = T (f z) (fmap f o) (fmap f e)
Given a value a
and a tree representing a function
f, it is easy to build another tree representing the
function g defined by g(0) = a
and
g(n+1) = f(n).
cons :: a -> T a -> T a cons a (T z o e) = T a (cons z e) o
It is also easy to represent the identity function.
infinite :: (Num i) => T i infinite = T 0 o (fmap (1+) o) where o = fmap (\n -> n * 2 + 1) infinite
The index
function converts a tree back to the
function on natural numbers that it represents.
index :: (Integral i) => T a -> i -> a index _ i | i < 0 = error "index: out of range" index (T z _ _) 0 = z index (T _ o e) i | k == 0 = index e (j-1) | otherwise = index o j where (j,k) = divMod i 2
Finally, given a natural number i
and two trees
representing the functions f and g, we can build
another tree representing the function h defined by
h(n) = f(n) if
n<i
, or
g(n−i
) otherwise.
takeAppend :: (Integral i) => i -> T a -> T a -> T a takeAppend i _ _ | i < 0 = error "takeAppend: out of range" takeAppend 0 _ t = t takeAppend i (T z1 o1 e1) (T z2 o2 e2) | k == 0 = T z1 (takeAppend j o1 o2 ) (takeAppend (j-1) e1 ze2) | otherwise = T z1 (takeAppend j o1 ze2) (takeAppend j e1 o2 ) where (j,k) = divMod i 2 ze2 = cons z2 e2
Now we can define find
. It uses the tree
representation above to skip constructing long unused parts of the
infinite bit sequence.
find :: (T Bool -> Bool) -> T Bool find p = b where b = fmap (\n -> p (takeAppend n b (cons True (find (\a -> p (takeAppend n b (cons True a))))))) infinite
That’s it!
Escardó tests find
by comparing computable
functions on integer sets.
type Cantor = Integer -> Bool forsome, forevery :: (Cantor -> Bool) -> Bool forsome p = p (index (find (p . index))) forevery p = not (forsome (not . p)) equal :: (Eq y) => (Cantor -> y) -> (Cantor -> y) -> Bool equal f g = forevery (\a -> f a == g a) coerce :: Bool -> Integer coerce True = 0 coerce False = 1 f',g',h' :: Cantor -> Integer f' a = a'(10*a'(3^80)+100*a'(4^80)+1000*a'(5^80)) where a' i = coerce (a i) g' a = a'(10*a'(3^80)+100*a'(4^80)+1000*a'(6^80)) where a' i = coerce (a i) h' a = a' k where i = if a'(5^80) == 0 then 0 else 1000 j = if a'(3^80) == 1 then 10+i else i k = if a'(4^80) == 0 then j else 100+j a' i = coerce (a i)
On my laptop, this code takes only several times more time and
space than Escardó’s to compare f'
, g'
,
and h'
to each other.
*Exhaustive> :set +s *Exhaustive> equal f' g' False (38.20 secs, 2786375560 bytes) *Exhaustive> equal f' h' True (14.10 secs, 1055343780 bytes) *Exhaustive> equal g' h' False (39.12 secs, 2762261544 bytes) *Exhaustive> equal f' f' True (13.87 secs, 1055610904 bytes) *Exhaustive> equal g' g' True (13.65 secs, 972683784 bytes) *Exhaustive> equal h' h' True (14.20 secs, 1054789196 bytes)
I take this experiment to show that a main essence of Escardó’s
algorithm is to represent a set of integers by a lazily evaluated
binary tree of bits. Maybe it’s worth defining a type class
Find
?
class Find a where find :: (a -> Bool) -> a
Cheers!