Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

existential search and query without the fuss

Is there an extensible, efficient way to write existential statements in Haskell without implementing an embedded logic programming language? Oftentimes when I'm implementing algorithms, I want to express existentially quantified first-order statements like

∃x.∃y.x,y ∈ xs ∧ x ≠ y ∧ p x y

where is overloaded on lists. If I'm in a hurry, I might write perspicuous code that looks like

find p []     = False
find p (x:xs) = any (\y -> x /= y && (p x y || p y x)) xs || find p xs

or

find p xs = or [ x /= y && (p x y || p y x) | x <- xs, y <- xs]

But this approach doesn't generalize well to queries returning values or predicates or functions of multiple arities. For instance, even a simple statement like

∃x.∃y.x,y,z ∈ xs ∧ x ≠ y ≠ z ∧ f x y z = g x y z

requires writing another search procedure. And this means a considerable amount of boilerplate code. Of course, languages like Curry or Prolog that implement narrowing or a resolution engine allow the programmer to write statements like:

find(p,xs,z) = x ∈ xs & y ∈ xs & x =/= y & f x y =:= g x y =:= z

to abuse the notation considerably, which performs both a search and returns a value. This problem arises often when implementing formally specified algorithms, and is often solved by combinations of functions like fmap, foldr, and mapAccum, but mostly explicit recursion. Is there a more general and efficient, or just general and expressive, way to write code like this in Haskell?

like image 713
emi Avatar asked Oct 18 '11 21:10

emi


2 Answers

There's a standard transformation that allows you to convert

∃x ∈ xs : P

to

exists (\x -> P) xs

If you need to produce a witness you can use find instead of exists.

The real nuisance of doing this kind of abstraction in Haskell as opposed to a logic language is that you really must pass the "universe" set xs as a parameter. I believe this is what brings in the "fuss" to which you refer in your title.

Of course you can, if you prefer, stuff the universal set (through which you are searching) into a monad. Then you can define your own versions of exists or find to work with the monadic state. To make it efficient, you can try Control.Monad.Logic, but it may involve breaking your head against Oleg's papers.

Anyway, the classic encoding is to replace all binding constructs, including existential and universal quantifiers, with lambdas, and proceed with appropriate function calls. My experience is that this encoding works even for complex nested queries with a lot of structure, but that it always feels clunky.

like image 144
Norman Ramsey Avatar answered Oct 26 '22 04:10

Norman Ramsey


Maybe I don't understand something, but what's wrong with list comprehensions? Your second example becomes:

[(x,y,z) | x <- xs, y <- xs, z <- xs
, x /= y && y /= z && x /= z 
, (p1 x y z) == (p2 x y z)]

This allows you to return values; to check if the formula is satisfied, just use null (it won't evaluate more than needed because of laziness).

like image 25
Mikhail Glushenkov Avatar answered Oct 26 '22 04:10

Mikhail Glushenkov