Systematically extract noun arguments from J expression - refactoring
What is the systematic approach to extracting nouns as arguments from an expression in J? To be clear, an expression containing two literals should become a dyadic expression with the left and right arguments used instead of the literals.
I'm trying to learn tacit style so I prefer not to use named variables if it is avoidable.
A specific example is a simple die roll simulator I made:
>:?10#6 NB. Roll ten six sided dice.
2 2 6 5 3 6 4 5 4 3
>:?10#6
2 1 2 4 3 1 3 1 5 4
I would like to systematically extract the arguments 10 and 6 to the outside of the expression so it can roll any number of any sized dice:
d =. <new expression here>
10 d 6 NB. Roll ten six sided dice.
1 6 4 6 6 1 5 2 3 4
3 d 100 NB. Roll three one hundred sided dice.
7 27 74
Feel free to illustrate using my example, but I'm looking to be able to follow the procedure for arbitrary expressions.
Edit: I just found out that a quoted version using x and y can be automatically converted to tacit form using e.g. 13 : '>:?x#y'. If someone can show me how to find the definition of 13 : I might be able to answer my own question.
If your goal is to learn tacit style, it's better that you simply learn it from the ground up rather than try to memorize an explicit algorithm—J4C and Learning J are good resources—because the general case of converting an expression from explicit to tacit is intractable.
Even ignoring the fact that there have been no provisions for tacit conjunctions since J4, in the explicit definition of a verb you can (1) use control words, (2) use and modify global variables, (3) put expressions containing x and/or y as the operands of an adverb or conjunction, and (4) reference itself. Solving (1), (3), or (4) is very hard in the general case and (2) is just flat out impossible.*
If your J sentence is one of a small class of expressions, there is an easy way to apply the fork rules make it tacit, and this is what is more or less what is implemented in 13 :. Recall that
(F G H) y is (F y) G (H y), and x (F G H) y is (x F y) G (x H y) (Monad/Dyad Fork)
([: G H) y is G (H y), and x ([: G H) y is G (x H y) (Monad/Dyad Capped Fork)
x [ y is x, x ] y is y, and both of [ y and ] y are y (Left/Right)
Notice how forks use their center verbs as the 'outermost' verb: Fork gives a dyadic application of g, while Capped Fork gives a monadic one. This corresponds exactly to the two modes of application of a verb in J, monadic and dyadic. So a quick-and-dirty algorithm for making tacit a "dyadic" expression might look like the following, for F G H verbs and N nouns:
Replace x with (x [ y) and y with (x ] y). (Left/Right)
Replace any other noun n with (x N"_ y)
If you see the pattern (x F y) G (x H y), replace it with x (F G H) y. (Fork)
If you see the pattern G (x H y), replace it with x ([: G H) y. (*Capped Fork()
Repeat 1 through 4 until you attain the form x F y, at which point you win.
If no more simplifications can be performed and you have not yet won, you lose.
A similar algorithm can be derived for "monadic expressions", expressions only dependent on y. Here's a sample derivation.
<. (y - x | y) % x NB. start
<. ((x ] y) - (x [ y) | (x ] y)) % (x [ y) NB. 1
<. ((x ] y) - (x ([ | ]) y)) % (x [ y) NB. 3
<. (x (] - ([ | ])) y) % (x [ y) NB. 3
<. x ((] - ([ | ])) % [) y NB. 3
x ([: <. ((] - ([ | ])) % [)) y NB. 4 and we win
This neglects some obvious simplifications, but attains the goal. You can mix in various other rules to simplify, like the long train rule—if Train is a train of odd length then (F G (Train)) are equivalent (F G Train)—or the observation that x ([ F ]) y and x F y are equivalent. After learning the rules, it shouldn't be hard to modify the algorithm to get the result [: <. [ %~ ] - |, which is what 13 : '<. (y - x | y) % x' gives.
The fail condition is attained whenever an expression containing x and/or y is an operand to an adverb or conjunction. It is sometimes possible to recover a tacit form with some deep refactoring, and knowledge of the verb and gerundial forms of ^: and }, but I am doubtful that this can be done programmatically.
This is what makes (1), (3), and (4) hard instead of impossible. Given knowledge of how $: works, a tacit programmer can find a tacit form for, say, the Ackermann function without too much trouble, and a clever one can even refactor that for efficiency. If you could find an algorithm doing that, you'd obviate programmers, period.
ack1 =: (1 + ])`(([ - 1:) $: 1:)`(([ - 1:) $: [ $: ] - 1:)#.(, i. 0:)
ack2 =: $: ^: (<:#[`]`1:) ^: (0 < [) >:
3 (ack1, ack2) 3
61 61
TimeSpace =: 6!:2, 7!:2#] NB. iterations TimeSpace code
10 TimeSpace '3 ack1 8'
2.01708 853504
10 TimeSpace '3 ack2 8'
0.937484 10368
* This is kind of a lie. You can refactor the entire program involving such a verb through some advanced voodoo magic, cf. Pepe Quintana's talk at the 2012 J Conference. It isn't pretty.
13 : is documented in the vocabulary or NuVoc under : (Explicit).
The basic idea is that the value you want to be x becomes [ and the value you want to be y becomes ]. But as soon as the the rightmost token changes from a noun (value) to a verb like [ or ], the entire statement becomes a train, and you may need to use the verb [: or the conjunctions # or #: to restore the composition behavior you had before.
You can also replace the values with the actual names x and y, and then wrap the whole thing in ((dyad : ' ... ')). That is:
>:?10#6 NB. Roll ten six sided dice.
can become:
10 (dyad : '>: ? x # y') 6 NB. dyad is predefined. It's just 4.
If you only need the y argument, you can use monad, which is prefined as 3. The name verb is also 3. I tend to use verb : when I provide both a monadic and dyadic version, and monad when I only need the monadic meaning.
If your verb is a one-liner like this, you can sometimes convert it automatically to tacit form by replacing the 3 or 4 with 13.
I have some notes on factoring verbs in j that can help you with the step-by-step transformations.
addendum: psuedocode for converting a statement to tacit dyad
This only covers a single statement (one line of code) and may not work if the constant values you're trying to extract are being passed to a conjunction or adverb.
Also, the statement must not make any reference to other variables.
Append [ x=. xVal [ y =. yVal to the statement.
Substitute appropriate values for xVal and yVal.
Rewrite the original expression in terms of the new x and y.
rewrite statement [ x=. xVal [ y=. yVal as:
newVerb =: (4 : 0)
statement ] y NB. we'll fill in x later.
)
(xVal) newVerb yVal
Now you have an explicit definition in terms of x and y. The reason for putting it on multiple lines instead of using x (4 : 'expr') y is that if expr still contains a string literal, you will have to fiddle with escaping the single quotes.
Converting the first noun
Since you only had a pipeline before, the rightmost expression inside statement must be a noun. Convert it to a fork using the following rules:
y → (])
x → ]x ([)
_, __, _9 ... 9 → (_:), (__:), (_9:) ... (9:)
n → n"_ (for any other arbitrary noun)
This keeps the overall meaning the same because the verb you've just created is invoked immediately and applied to the [ y.
Anyway, this new tacit verb in parentheses becomes the core of the train you will build. From here on out, you work by consuming the rightmost expression in the statement, and moving it inside the parentheses.
Fork normal form
From here on out, we will assume the tacit verb we're creating is always a fork.
This new tacit verb isn't actually a fork, but we will pretend it is, because any single-token verb can be rewritten as a fork using the rule:
v → ([: ] v).
There is no reason to actually do this transformation, it's just so I can simplify the rule below and always call it a fork.
We will not use hooks because any hook can be rewritten as a fork with the rule:
(u v) → (] u [: v ])
The rules below should produce trains in this form automatically.
Converting the remaining tokens
Now we can use the following rules to convert the rest of the original pipeline, moving one item at a time into the fork.
For all of these rules, the (]x)? isn't J syntax. It means the ]x may or may not be there. You can't put the ] x in until you transform a usage of x without changing the meaning of the code. Once you transform an instance of x, the ]x is required.
Following the J convention, u and v represent arbitrary verbs, and n is an arbitrary noun. Note that these include verbs
tokens y u (]x)? (fork) ] y → tokens (]x)? (] u fork) ] y
tokens x u (]x)? (fork) ] y → tokens ]x ([ u fork) ] y
tokens n u (]x)? (fork) ] y → tokens (]x)? (n u fork) ] y
tokens u v (]x)? (fork) ] y → tokens u (]x)? ([: v fork) ] y
There are no rules for adverbs or conjunctions, because you should just treat those as part of the verbs. For example +:^:3 should be treated as a single verb. Similarly, anything in parentheses should be left alone as a single phrase.
Anyway, keep applying these rules until you run out of tokens.
Cleanup
You should end up with:
newVerb =: (4 : 0)
] x (fork) ] y
)
(xVal) newVerb yVal
This can be rewritten as:
(xVal) (fork) yVal
And you are done.
Related
Switch from dyadic to monadic interpretation in a J sentence
I am trying to understand composition in J, after struggling to mix and match different phases. I would like help switching between monadic and dyadic phrases in the same sentence. I just made a simple dice roller in J, which will serve as an example: d=.1+[:?[#] 4 d 6 2 3 1 1 8 d 12 10 2 11 11 5 11 1 10 This is a chain: "d is one plus the (capped) roll of x occurrences of y" But what if I wanted to use >: to increment (and skip the cap [: ), such that it "switched" to monadic interpretation after the first fork? It would read: "d is the incremented roll of x occurrences of y". Something like this doesn't work, even though it looks to me to have about the right structure: d=.>:&?[#] d >:&? ([ # ]) (If this approach is against the grain for J and I should stick to capped forks, that is also useful information.)
Let's look at a dyadic fork a(c d f h g)b where c,d,f, g and h are verbs and a and b are arguments, which is evaluated as: (a c b) d (a f b) h (a g b) The arguments are applied dyadically to the verbs in the odd positions (or tines c,f and g) - and those results are fed dyadically right to left into the even tines d and h. Also a fork can be either in the form of (v v v) or (n v v) where v stands for verbs and n stands for nouns. In the case of (n v v) you just get the value of n as the left argument to the middle tine. If you look at your original definition of d=.1+[:?[#] you might notice it simplifies to a dyadic fork with five tines (1 + [: ? #) where the [ # ] can be replaced by # as it is a dyadic fork (see definition above). The [: (Cap) verb returns no value to the left argument of ? which means that ? acts monadically on the result of a # b and this becomes the right argument to + which has a left argument of 1. So, on to the question of how to get rid of the [: and use >: instead of 1 + ... You can also write ([: f g) as f#:g to get rid of the Cap, which means that ([: ? #) becomes ?#:# and now since you want to feed this result into >: you can do that by either: d1=.>:#:?#:# d2=. [: >: ?#:# 4 d1 6 6 6 1 5 4 d2 6 2 3 4 5 8 d1 12 7 6 6 4 6 9 8 7 8 d2 12 2 10 10 9 8 12 4 3 Hope this helps, it is a good fundamental question about how forks are evaluated. It would be your preference of whether you use the ([: f g) or f#:g forms of composition.
To summarize the main simple patterns of verb mixing in J: (f #: g) y = f (g y) NB. (1) monadic "at" x (f #: g) y = f (x g y) NB. (2) dyadic "at" x (f &: g) y = (g x) f (g y) NB. (3) "appose" (f g h) y = (f y) g (h y) NB. (4) monadic fork x (f g h) y = (x f y) g (x h y) NB. (5) dyadic fork (f g) y = y f (g y) NB. (6) monadic hook x (f g) y = x f (g y) NB. (7) dyadic hook A nice review of those is here (compositions) and here (trains). Usually there are many possible forms for a verb. To complicate matters more, you can mix many primitives in different ways to achieve to same result. Experience, style, performance and other such factors influence the way you'll combine the above to form your verb. In this particular case, I would use #bob's d1 because I find it clearer to read: increase the roll of x copies of y: >: # ? # $ For the same reason, I am replacing # with $. When I see # in this context, I automatically read "number of elements of", but maybe that's just me.
Defining a mathematical language in prolog
So I have this mathematical language, it goes like this: E -> number [+,E,E,E] //e.g. [+,1,2,3] is 1+2+3 %we can put 2 to infinite Es here. [-,E,E,E] //e.g. [-,1,2,3] is 1-2-3 %we can put 2 to infinite Es here. [*,E,E,E] //e.g. [*,1,2,3] is 1*2*3 %we can put 2 to infinite Es here. [^,E,E] //e.g. [^,2,3] is 2^3 [sin,E] //e.g. [sin,0] is sin 0 [cos,E] //e.g. [cos,0] is cos 0 and I want to write the set of rules that finds the numeric value of a mathematical expression written by this language in prolog. I first wrote a function called "check", it checks to see if the list is written in a right way according to the language we have : check1([]). check1([L|Ls]):- number(L),check1(Ls). check([L|Ls]):-atom(L),check1(Ls). now I need to write the function "evaluate" that takes a list that is an expression written by this language, and a variable that is the numeric value corresponding to this language. example: ?-evaluate([*,1,[^,2,2],[*,2,[+,[sin,0],5]]]],N) -> N = 40 so I wrote this: sum([],0). sum([L|Ls],N):- not(is_list(L)),sum(Ls,No),N is No + L. min([],0). min([L|Ls],N):-not(is_list(L)), min(Ls,No),N is No - L. pro([],0). pro([X],[X]). pro([L|Ls],N):-not(is_list(L)), pro(Ls,No), N is No * L. pow([L|Ls],N):-not(is_list(L)), N is L ^ Ls. sin_(L,N):-not(is_list(L)), N is sin(L). cos_(L,N):-not(is_list(L)), N is cos(L). d([],0). d([L|Ls],N):- L == '+' ,sum(Ls,N); L == '-',min(Ls,N); L == '*',pro(Ls,N); L == '^',pow(Ls,N); L == 'sin',sin_(Ls,N); L == 'cos',cos_(Ls,N). evaluate([],0). evaluate([L|Ls],N):- is_list(L) , check(L) , d(L,N),L is N,evaluate(Ls,N); is_list(L), not(check(L)) , evaluate(Ls,N); not(is_list(L)),not(is_list(Ls)),check([L|Ls]),d([L|Ls],N), L is N,evaluate(Ls,N); is_list(Ls),evaluate(Ls,N). and it's working for just a list and returning the right answer , but not for multiple lists inside the main list, how should my code be?
The specification you work with looks like a production rule that describes that E (presumably short for Expression) might be a number or one of the 6 specified operations. That is the empty list [] is not an expression. So the fact evaluate([],0). should not be in your code. Your predicate sum/2 almost works the way you wrote it, except for the empty list and a list with a single element, that are not valid inputs according to your specification. But the predicates min/2 and pro/2 are not correct. Consider the following examples: ?- sum([1,2,3],X). X = 6 % <- correct ?- sum([1],X). X = 1 % <- incorrect ?- sum([],X). X = 0 % <- incorrect ?- min([1,2,3],X). X = -6 % <- incorrect ?- pro([1,2,3],X). X = 6 ? ; % <- correct X = 0 % <- incorrect Mathematically speaking, addition and multiplication are associative but subtraction is not. In programming languages all three of these operations are usually left associative (see e.g. Operator associativity) to yield the mathematically correct result. That is, the sequence of subtractions in the above query would be calculated: 1-2-3 = (1-2)-3 = -4 The way you define a sequence of these operations resembles the following calculation: [A,B,C]: ((0 op C) op B) op A That works out fine for addition: [1,2,3]: ((0 + 3) + 2) + 1 = 6 But it doesn't for subtraction: [1,2,3]: ((0 - 3) - 2) - 1 = -6 And it is responsible for the second, incorrect solution when multiplying: [1,2,3]: ((0 * 3) * 2) * 1 = 0 There are also some other issues with your code (see e.g. #lurker's comments), however, I won't go into further detail on that. Instead, I suggest a predicate that adheres closely to the specifying production rule. Since the grammar is describing expressions and you want to know the corresponding values, let's call it expr_val/2. Now let's describe top-down what an expression can be: It can be a number: expr_val(X,X) :- number(X). It can be an arbitrarily long sequence of additions or subtractions or multiplications respectively. For the reasons above all three sequences should be evaluated in a left associative way. So it's tempting to use one rule for all of them: expr_val([Op|Es],V) :- sequenceoperator(Op), % Op is one of the 3 operations exprseq_op_val(Es,Op,V). % V is the result of a sequence of Ops The power function is given as a list with three elements, the first being ^ and the others being expressions. So that rule is pretty straightforward: expr_val([^,E1,E2],V) :- expr_val(E1,V1), expr_val(E2,V2), V is V1^V2. The expressions for sine and cosine are both lists with two elements, the first being sin or cos and the second being an expression. Note that the argument of sin and cos is the angle in radians. If the second argument of the list yields the angle in radians you can use sin/1 and cos/2 as you did in your code. However, if you get the angle in degrees, you need to convert it to radians first. I include the latter case as an example, use the one that fits your application. expr_val([sin,E],V) :- expr_val(E,V1), V is sin(V1*pi/180). % radians = degrees*pi/180 expr_val([cos,E],V) :- expr_val(E,V1), V is cos(V1*pi/180). % radians = degrees*pi/180 For the second rule of expr_val/2 you need to define the three possible sequence operators: sequenceoperator(+). sequenceoperator(-). sequenceoperator(*). And subsequently the predicate exprseq_op_val/3. As the leading operator has already been removed from the list in expr_val/2, the list has to have at least two elements according to your specification. In order to evaluate the sequence in a left associative way the value of the head of the list is passed as an accumulator to another predicate exprseq_op_val_/4 exprseq_op_val([E1,E2|Es],Op,V) :- expr_val(E1,V1), exprseq_op_val_([E2|Es],Op,V,V1). that is describing the actual evaluation. There are basically two cases: If the list is empty then, regardless of the operator, the accumulator holds the result. Otherwise the list has at least one element. In that case another predicate, op_val_args/4, delivers the result of the respective operation (Acc1) that is then recursively passed as an accumulator to exprseq_op_val_/4 alongside with the tail of the list (Es): exprseq_op_val_([],_Op,V,V). exprseq_op_val_([E1|Es],Op,V,Acc0) :- expr_val(E1,V1), op_val_args(Op,Acc1,Acc0,V1), exprseq_op_val_(Es,Op,V,Acc1). At last you have to define op_val_args/4, that is again pretty straightforward: op_val_args(+,V,V1,V2) :- V is V1+V2. op_val_args(-,V,V1,V2) :- V is V1-V2. op_val_args(*,V,V1,V2) :- V is V1*V2. Now let's see how this works. First your example query: ?- expr_val([*,1,[^,2,2],[*,2,[+,[sin,0],5]]],V). V = 40.0 ? ; no The simplest expression according to your specification is a number: ?- expr_val(-3.14,V). V = -3.14 ? ; no The empty list is not an expression: ?- expr_val([],V). no The operators +, - and * need at least 2 arguments: ?- expr_val([-],V). no ?- expr_val([+,1],V). no ?- expr_val([*,1,2],V). V = 2 ? ; no ?- expr_val([-,1,2,3],V). V = -4 ? ; no The power function has exactly two arguments: ?- expr_val([^,1,2,3],V). no ?- expr_val([^,2,3],V). V = 8 ? ; no ?- expr_val([^,2],V). no ?- expr_val([^],V). no And so on...
Prolog - adding two arguments, even if one is not a number
in Prolog, how should I proceed when I want to add two arguments, even if one is not a number. So for instance, if I enter add2args(1,2,R). the result should be R = 3. If I enter add2args(1,x,R). the result should be R=1+x. So far I have this: add_2args(X,Y,R):- number(X),number(Y), R is (X+Y). Which allows me to add two numbers, but I don't know how I can get it to print out anything other than true and false if X and Y are not numbers which is normal since number(X) checks if X is a number or not. What other rule do I have to add to get the desired result?
Prolog will view an expression symbolically (as a Prolog term) unless explicitly evaluated with something like is/2. So the simplest way to do this in your case would be the following: add_2args(X, Y, R) :- ( number(X), number(Y) % Both X and Y are numbers, then... -> R is X + Y % Evaluate the expression ; R = X + Y % Else, just unify R with the expression ). The R = X + Y will not evaluate the expression but only unify the term X + Y with R. This is also a nice "Prolog beginner's guide" illustration for the difference between =/2 and is/2. If you wrote, for example, R = 2 + 3, then did a write(R) you would see 2 + 3, not 5. You could subsequently do, Result is R which would then evaluate the expression R and yield Result = 5. | ?- R = 2 + 3, Result is R. R = 2+3 Result = 5 yes | ?-
Evaluating an algebraic expression
This is a test review question that I am having trouble with. How do you write a method to evaluate an algebraic expression with the operators plus, minus and times. Here are some test queries: simplify(Expression, Result, List) ?- simplify(plus(times(x,y),times(3 ,minus(x,y))),V,[x:4,y:2]). V = 14 ?- simplify(times(2,plus(a,b)),Val,[a:1,b:5]). Val = 12 ?- simplify(times(2,plus(a,b)),Val,[a:1,b:(-5)]). Val = -8 . All I was given were these sample queries and no other explanation. But I am pretty sure the method is supposed to dissect the first argument, which is the algebraic expression, substituting x and y for their values in the 3rd argument (List). The second argument should be the result after evaluating the expression. I think one of the methods should be simplify(V, Val, L) :- member(V:Val, L). Ideally there should only be 4 more methods... but I'm not sure how to go about this.
Start small, write down what you know. simplify(plus(times(x,y),times(3 ,minus(x,y))),V,[x:4,y:2]):- V = 14. is a perfectly good start: (+ (* 4 2) (* 3 (- 4 2))) = 8 + 3*2 = 14. But then, of course, simplify(times(x,y),V,[x:4,y:2]):- V is 4*2. is even better. Also, simplify(minus(x,y),V,[x:4,y:2]):- V is 4-2. simplify(plus(x,y),V,[x:4,y:2]):- V is 4+2. simplify(x,V,[x:4,y:2]):- V is 4. all perfectly good Prolog code. But of course what we really mean, it becomes apparent, is simplify(A,V,L):- atom(A), getVal(A,L,V). simplify(C,V,L):- compound(C), C =.. [F|T], maplist( simp(L), T, VS), % get the values of subterms calculate( F, VS, V). % calculate the final result simp(L,A,V):- simplify(A,V,L). % just a different args order etc. getVal/3 will need to retrieve the values somehow from the L list, and calculate/3 to actually perform the calculation, given a symbolic operation name and the list of calculated values. Study maplist/3 and =../2. (not finished, not tested). OK, maplist was an overkill, as was =..: all your terms will probably be of the form op(A,B). So the definition can be simplified to simplify(plus(A,B),V,L):- simplify(A,V1,L), simplify(B,V2,L), V is V1 + V2. % we add, for plus simplify(minus(A,B),V,L):- % fill in the blanks ..... V is V1 - V2. % we subtract, for minus simplify(times(A,B),V,L):- % fill in the blanks ..... V is .... . % for times we ... simplify(A,V,L):- number(A), V = .... . % if A is a number, then the answer is ... and the last possibility is, x or y etc., that satisfy atom/1. simplify(A,V,L):- atom(A), retrieve(A,V,L). So the last call from the above clause could look like retrieve(x,V,[x:4, y:3]), or it could look like retrieve(y,V,[x:4, y:3]). It should be a straightforward affair to implement.
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?
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.
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).