0

we are currently working on the implementation of the predicate reduce, which Needs to take a list L, a binary function F that operates on the elements of L and has neutral element N and maps them to a number E that is obtained by applying F to the elements of L recursively.

Here's our Code so far:

reduce([],F,NEUTRAL,EE).

reduce([H|T],F,NEUTRAL,EE) :-
    Goal =.. [F, H, NEUTRAL, EE],
    call(Goal),
    reduce(T,F,NEUTRAL,EE).

add(N1,NEUTRAL, EE) :- EE is EE + N1 + NEUTRAL.

mult(N1,NEUTRAL, EE) :- EE is N1 * EE * NEUTRAL.

the Problem is: to same sample queries such as ?- reduce([], add, 0, R). we just get a boolean (in this case true), to others an error back, which tells us that our Arguments are not properly instantiated. What we actually aim for is a Response like R = 0. (for the example given above).

Hope you can help us, please do not be too complicated as we are Prolog beginners ;)

coder
  • 12,832
  • 5
  • 39
  • 53
  • 3
    You can replace `Goal =.. [F, H, NEUTRAL, EE], call(Goal)` by `call(F, H, NEUTRAL, EE)` – false May 24 '17 at 19:26
  • 2
    Your base case `reduce([], F, NEUTRAL, EE)` has too many variables (you probably saw the "singleton" warnings). This base case should answer the question of *What should `EE` be if the list is empty?* (And in this case you probably don't care what `F` and `NEUTRAL` are.) So the base case would be come `reduce([], _, _, ??)`.. what's `??`? Is it `0`? The "not properly instantiated" error occurs when you try to do a computation like `EE is N1 * EE * NEUTRAL` and one of the variables on the right doesn't have a value. – lurker May 24 '17 at 19:51
  • 1
    Finally, you cannot re-instantiate a variable once it's instantiated within a predicate clause. So `EE is N1 * EE * NEUTRAL` will can fail unless the `EE` on the left is the same value as the `EE` on the right (they're the same `EE`). – lurker May 24 '17 at 19:53
  • 1
    how can we perform arthmetic Operation on a variable? Can we assign the value of EE? We tried it with equals and is, but this does not work either. –  May 24 '17 at 21:50
  • 3
    You can use CLP(FD) if you want to express arithmetic on integers when the values aren't yet known: `EE1 #= N1 * EE * NEUTRAL`. And, yes, `EE` can be *bound* to a value, it just cannot be bound again without backtracking. Remember: Prolog isn't just like C or C++ or Java, just with different syntax. It's a language for defining facts and rules. When a variable is bound, Prolog attempts to use that binding as the predicate continues to execute. To get re-bound, backtracking must occur due to failure of subsequent expressions. – lurker May 25 '17 at 00:19
  • 3
    "... we are Prolog beginners" is this the royal We or are You actually a group of people? –  May 25 '17 at 05:10
  • 2
    @Boris: [next](https://stackoverflow.com/q/44200008/772868) – false May 26 '17 at 11:59

4 Answers4

3

You need the neutral element to end the recursion. You don't need it at every step. I'd almost say that the neutral element belongs with the operator, not with the reduce. Something like this:

operation_neutral(add, 0).
operation_neutral(mult, 1).

Then, your reduce could look like this:

reduce([], Op, Result) :- operation_neutral(Op, Result).
reduce([X|Xs], Op, Result) :-
    reduce(Xs, Op, R0),
    call(Op, X, R0, Result).

But let's keep it easy for now and do it as suggested in the question. You need the neutral element only if the list of operands is empty, to set your result:

reduce([], _, N, N).

Otherwise, go in the recursion and then apply the operation to the result (and you can use call directly):

reduce([X|Xs], Op, N, R) :-
    reduce(Xs, Op, N, R0),
    call(Op, X, R0, R).

You can see here how to use reduce/4 for example with append/3.

Do you see N inside call? I do not see it, and it doesn't belong there.

For completeness, the two operators:

add(X, Y, R) :- R is X+Y.
mult(X, Y, R) :- R is X*Y.

There is no mention of a neutral element either.


By the way: what you have called "reduce" is also known as a "fold" over a list. You could define your reduce/3 in terms of foldl like this:

reduce(List, Op, Result) :-
    operation_neutral(Op, N),
    foldl(Op, List, N, Result).

The recursion and the call both happen inside foldl, you don't need reduce/4 at all.


How would you solve this in a procedural language? Using pseudo-code with a C-like syntax, and assuming we have some support for generics, it could go like this:

template <typename A, typename Op>
A reduce(A[] operands, Op op) {
    return reduce(operands, op, 0);
}

template <typename A, typename Op>
A reduce(A[] operands, Op op, int n) {
    if (n == operands.length) return neutral<Op>();
    return op(operands[n], reduce(operands, op, n+1));
}

(apparently if this was indeed C++ we would be using iterators, not an index...)

It is not at all that different from the Prolog version. And if you did it the same way as the foldl above, it would probably look like this:

template <typename A, typename Op>
A reduce(A[] operands, Op op) {
    A x = neutral<Op>();
    for (int n = 0; n < operands.length; ++n)
        x = op(x, operands[n]);
    return x;
}

Again, no mention of the neutral element inside the loop, nor in the call to op().

3

Based to the comments above and the fact that you're still Prolog beginner, I will try to explain some things that may not be very clear:

What are variables in Prolog and what is instantiation ?

In Prolog variables are whatever starts with Capital letters and the anonymous variables _ (just an underscore defines an anonymous variable which is a variable without a name). Variables in Prolog means means that they could represent anything a list, a string, an atom (...whatever), when you use a new variable it is not instantiated which means it could represent anything but right now it does represent- it hasn't being bind with a list, atom... When you want to instantiate a variable you could do that:

  • by using unification =/2 for example X = 2 unifies-instantiates variable X with number 2.
  • or by using is/2 only for expressions like X is 2 does the same as above.

Very important is that when a variable in Prolog is instantiated we know what it represents and we can not change for example X =2, X = 3 will fail!! since we know that X binds with 2 and we try to reinstantiate with 3 which fails.

What is =/2, is/2, and what's the difference between these two prediactes ??

  • is/2 :it returns the result of an arithmetical expression to an uninstantiated variable. This means that you can use it like: X is 2, or X is mod(Z,Y) but it is important to notice here that whatever is in the expression MUST be instantiated if it is a variable like Y and Z previously. If the expression is not instantiated you will get an instantiation error as in your case. Even simple things like 2 is X+1 will fail with instantiation error if X is not instantiated so don't expect with is/2 to return something like X=1 in previous case which is an obvious answer (but not unique so it would be inconsistent!). Also arithmetic expression means that you could not use something like X is [], to unify X with empty list this would of course fail, so in order to do that use =/2, described below.

  • =/2 :This is used to unify a variable (or in general a term) with another term. For example you could use it like: X = 2 to unify X with number 2, or X = [1, 2] to unify X with a list or X = 1 + 2 which will not give 3 but just the term 1 + 2, the last two examples I think clearly shows the difference with is/2.

But then how could we use arithmetic expressions like is/2 but more relational ?

The answer here is library CLPFD (as recommended by @lurker in the comments) all you have to do is place :- use_module(library(clpfd)). in your file and use the operator #= instead of is/2. Now you could try X #= 2, which will instantiate variable X with number 2 or even 2 #= 1 + X and now you get no instantiation error but you get the answer X = 1, so you can see that it is now much more relational.

Now to the question...

I agree with @Boris neutral should not be carried through recursion but declared with the function. Though I will try to keep up with your answer-attempt:

First of all when try to consult a file with your code in swi prolog I get:

Warning: /Users/Desktop/reduce.pl:1:
    Singleton variables: [F,NEUTRAL,EE]

This says variables F,Neutral,EE are singleton it this clause:

reduce([],F,NEUTRAL,EE). 

As you can see you don't use anywhere in this clause for example variable F.

To my opinion the reduce predicate should fail with empty list since you can't do any operation with the neutral element and a function which takes two parameters. So as base case I would define using a list with just one element:

reduce([H],F,NEUTRAL,EE):-
        Goal =.. [F, H, NEUTRAL, EE], 
        call(Goal).

Then as explained above rules like:

add(N1,NEUTRAL, EE) :- EE is EE + N1 + NEUTRAL.

will cause instantiation error since in the EE is var and you can't use it in is/2 but even if it was instantiated you couldn't also use it because as explained EE is EE + whatever will not work because you try to reinstantiate a variable..!!

In order to fix it I would try to use another variable for the next recursion and then calculate the result like:

 reduce([H],F,NEUTRAL,EE):-
     Goal =.. [F, H, NEUTRAL, EE], 
     call(Goal).
  reduce([H|T],F,NEUTRAL,EE) :-
     reduce(T,F,NEUTRAL,EE1),
     Goal =.. [F, H, EE1, EE],
     call(Goal).

(Here added a new variable EE1 do the recursive call and then calculate the result based on the Result of EE1 that will be returned from recursive call.)

Now let's test it:

?- reduce([1,2,3,4],add,0,L).
L = 10 ;
false.

?- reduce([1,2,3,4],mult,1,L).
L = 24 ;
false.

One more thing when it gave you the result L=10 by pressing ";" we ask for more answers and returns false since there are no possible answers. The false or true things are just a way that Prolog's toplevel informs you if the predicate succeeds or fails.For example:

?- reduce([1,2,3,4],mult,1,24).
true ;
false.

This states that the above is true and when we ask if there are any other ways that this could be true it returns false... So that's it, after all Prolog doesn't seems to be that bad :) (according to your description).
As an exercise you could try to write the same using CLPFD in order to be more relational and be able to answer queries like: reduce(L,mult,1,24). and maybe @Boris's idea about not carrying the neutral all the way through recursion.


EDIT

According to @false suggestion and useful comments I will write a second way using call/N which is clearly better(see comments...):

reduce([H],F,NEUTRAL,EE):-
        call(F, H, NEUTRAL, EE).
reduce([H|T],F,NEUTRAL,EE) :-
        reduce(T,F,NEUTRAL,EE1),
        call(F, H, EE1, EE).
false
  • 10,264
  • 13
  • 101
  • 209
coder
  • 12,832
  • 5
  • 39
  • 53
  • 4
    Oh please, use `call/N`. – false May 25 '17 at 20:40
  • @false, exactly same conversation had before with lurker :)!! I agree with your suggestion but @Anna said (in @Boris answer comments) that needs to use `=..` (I guess homework...) so that's the reason... – coder May 25 '17 at 20:42
  • 1
    @coder: Then be so nice to her and tell her that the exercises should be up-dated! – false May 25 '17 at 20:43
  • 2
    @coder: Simply repeat what has to be said! Look, call/N has been here since 1984. But it did not make it into the 1995 standard because so many (including system implementors) did not understand it at that time. Only in Cor.2:2012 this error has been corrected. Whenever `call/N` can be used instead, `(=..)/2` is simply evil! – false May 25 '17 at 21:32
  • @coder: And there are much deeper reasons like `call/N` does type but `(=..)/2` does not... – false May 25 '17 at 21:32
  • @false, what do you mean _`call/N` does type_ ? what really does this means? – coder May 25 '17 at 21:39
  • 2
    @coder: It fits into the original Mycroft O'Keefe type system. In fact, the meta_predicate declarations are a very simple form of type declaration. – false May 25 '17 at 21:47
  • @false, edited the answer and added your suggestion, still not an efficient solution but `call/N` should definitely be mentioned, thanks for the useful comments !! – coder May 26 '17 at 08:20
  • One more, writing in place of `F` rather `F_3` within `call(F, H, EE1, EE)` helps to separate meta-arguments like this from regular arguments. – false May 26 '17 at 10:01
  • Y U use `reduce([H], ...)` and `reduce([H|T], ...)` and not `reduce([], ...)` and `reduce([H|T], ...)` i try to understand what is your reason but only reason i can find is that you want to use `=..` and `call` not once but twice or maybe you have better reasons? –  May 26 '17 at 10:22
  • I cannot give you answer in comment and I don't want to write answer to this question because it will answer the wrong question, but I am quite positively certain that `reduce([], ...)` has well defined meaning, and this meaning is "the neutral element" but you probably know better math and programming than me and I am wrong maybe? You should ask a question if you really want answers. –  May 26 '17 at 19:40
  • @ User9213, you have right about the picture thing what is done puts the blame to whoever did it so forget it...As for the empty list I've seen (both in stack overflow and in university) plenty of examples with two variable functions there is nothing defined about just giving the neutral element. This examples are widely used in functional programming Haskell-SML-OCAML and others where foldr,foldl and other high order functions are widely used but never defined operation with only giving a neutral element so in my opinion reduce([],...) with empty list should definitely fail... – coder May 26 '17 at 21:08
  • `reduce` is about as old as Lisp and you can just google "Lisp reduce" and decide for yourself how it is better to implement! –  May 27 '17 at 04:14
-2

I hope this partial answer is not too off-topic thus voiding it from inclusion in this discussion .

Regarding the OP statement "to same sample queries such as ?- reduce([], add, 0, R). we just get a boolean (in this case true)" it is important to have a good interpretation of what you mean by "true" . In my opinion it is NOT appropriate to consider that "return value" you identified (.i.e. the (implicit) return value) as having a "boolean" type . This is because with a boolean type >>"false" means the opposite to true<< . In prolog in that particular context (.i.e. with regard to the (implicit) return value) the application of the boolean type expectation does not apply , because in the case of that type and nonalike the boolean type --- >>"false" means something different than "true"<< .

user229044
  • 232,980
  • 40
  • 330
  • 338
Kintalken
  • 763
  • 5
  • 9
  • 1
    Please don't sign your posts. Signatures and taglines are disallowed in your posts, as they are already "signed" with your user card and we strive to keep the contents if your questions and answers free from nonessential text. – user229044 May 31 '17 at 18:38
-2

As mentioned in the earlier answers you really want to eliminate your dependency upon call/99 .

Here is something to get you started . It is your original completely dysfunctional code with a different syntax . What I mean is that I hope to present a syntaz alternativ that might be useful to you but the broken semantiiks of the original remain nonchanged .

  % [ reduce , VECTOR , FUNCTION , NEUTRAL , EE ] 

  :- [library(clpfd)] .

  [reduce , [] , F , NEUTRAL , EE] :- ( true ) .

  [reduce , [N|NN] , F , NEUTRAL , EE]
  :-
  (
      [F , N , NEUTRAL , EE] ,
      [reduce , NN , Z , NEUTRAL , EE] 
  )
  .

  [add , N , NEUTRAL , EE] :- ( EE #= EE + N + NEUTRAL ) .

  [mult , N , NEUTRAL , EE] :- ( EE #= N * EE * NEUTRAL ) .

Here is the original for convenient comparison ...

  %

  reduce([],F,NEUTRAL,EE).

  reduce([H|T],F,NEUTRAL,EE) :-
      Goal =.. [F, H, NEUTRAL, EE],
      call(Goal),
      reduce(T,F,NEUTRAL,EE).

  add(N1,NEUTRAL, EE) :- EE is EE + N1 + NEUTRAL.

  mult(N1,NEUTRAL, EE) :- EE is N1 * EE * NEUTRAL.
user229044
  • 232,980
  • 40
  • 330
  • 338
Kintalken
  • 763
  • 5
  • 9
  • 4
    Redefining [ _ | _ ] as you do, doesn't work. You use it yourself in your code for consult, in the statement :- [library(clpfd)]. It will not work since [ _ | _ ] is a system predicate you shouldn't override it. Your code is only ASCII art. See also https://groups.google.com/forum/#!topic/swi-prolog/3xs7xNwqLOo –  May 31 '17 at 23:45
  • 2
    Comments are for requesting clarification, not for open-ended discussion. Please stop leaving long strings of comments below your posts, they will continue to be removed. – user229044 Jul 24 '17 at 18:42