How do I assign return variable in prolog?

Viewed 86

I'm totally new to Prolog so I might be really off here. I'm trying to solve the following problem for the past week:

tweeted(anne, tweet1).
tweeted(anne, tweet5).
tweeted(fred, tweet2).
tweeted(fred, tweet7).
tweeted(fred, tweet8).

findTweets(Name, TweetsByName) :- findall(X, tweeted(Name, X), TweetsByName).

tweets([], _).
tweets([Name|Names], Result) :- 
    findTweets(Name, Result),     
/* how do I append Result to Result ? */
    tweets(Names, Result).

If I call with single variable, there's only one recursion call and I get result:

?- tweets([fred], R).
R = [tweet2, tweet7, tweet8].

But I'm struggling to understand how can I append Result value from findTweets call to Result in order to return accumulative result? I tried using append function but I had no luck... for example:

tweets([], _).
tweets([Name|Names], Result) :- 
    findTweets(Name, Tweets),     
    append(Tweets, Result, Temp),
    Result is Temp,
    tweets(Names, Result).

But I get this error:

?- tweets([fred], R).
ERROR: Type error: `character' expected, found `tweet2' (an atom)
ERROR: In:
ERROR:   [11] _25712 is [tweet2,tweet7|...]
ERROR:   [10] tweets([fred],_25752) at /home/kimchi/git-repos/bbk/Programming-Language-Paradigms/logic-programming-Pavel-Durov/relationships.pl:33
ERROR:    [9] toplevel_call(user:user: ...) at /snap/swi-prolog/43/usr/lib/swipl/boot/toplevel.pl:1117

Thanks in advance :)

3 Answers

First, a few notes:

  • is is only for arithmetic computations on numbers. You cannot use it on lists, you will get an error.
  • You can never "re-assign" variables in Prolog. You always need to use a new variable if you want to want a name for a new term.

Apart from that, you are on the right track with append. My solution is close to yours, it just needed to be rearranged a bit:

persons_tweets([], []).
persons_tweets([Person | Persons], AllTweets) :-
    person_tweets(Person, ThisPersonTweets),
    persons_tweets(Persons, OtherPersonsTweets),
    append(ThisPersonTweets, OtherPersonsTweets, AllTweets).

I renamed your findTweets to person_tweets to make clearer that it is a relation between one person and a collection of tweets. Similarly, persons_tweets is a relation between a collection (list) of persons and tweets.

Example:

?- persons_tweets([fred, anne], Tweets).
Tweets = [tweet2, tweet7, tweet8, tweet1, tweet5].

Note that the non-recursive case is persons_tweets([], []). You cannot use the anonymous variable _ here. If you have an empty list of persons, you really want to have an empty list of tweets, not just any tweets. For instance, this should fail, but with your version it would succeed:

?- persons_tweets([], [some_tweet]).
false.

I think you want:

?- Names = [fred], findall(Tweet, (member(Name, Names), tweeted(Name, Tweet)), Tweets).
Names = [fred],
Tweets = [tweet2,tweet7,tweet8].

?- Names = [anne, fred], findall(Tweet, (member(Name, Names), tweeted(Name, Tweet)), Tweets).
Names = [anne,fred],
Tweets = [tweet1,tweet5,tweet2,tweet7,tweet8].

This is listing all the tweets that were tweeted by the Names.

There's no reason to use recursion here. You can do it just with findall/3. Try something like this:

tweet( anne , tweet1 ).
tweet( anne , tweet5 ).
tweet( fred , tweet2 ).
tweet( fred , tweet7 ).
tweet( fred , tweet8 ).

tweets( Ps , Ts ) :- findall(T,desired_tweet(Ps,T),Ts).

desired_tweet(Ps,T) :- tweet(P,T), member(P,Ps).

So tweets([fred],Ts) yields the expected Ts = [tweet1, tweet5, tweet2, tweet7, tweet8].

But it fails horribly if the list of authors is a variable. And it's nice if prolog predicates behave symmetrically. So lets add a couple of helpers here:

This lets the user specify a single name as an atom (a likely common use case):

tweets( P  , Ts ) :- atom(P) , ! , tweets([P],Ts) .

And this lets the user leave the persons list as an unbound variable, which will results in retrieving all the tweets (and generating a list of authors):

tweets( Ps , Ts ) :- var(Ps) , ! , setof(P,T^tweet(P,T),Ps) , tweets(Ps,Ts) .

[Note: the expression in the setof/3 invocation, T^tweet(P,T) is an existential quantifier. It tells setof/3 to ignore the values of T in determining the set, so you get the distinct set of P rather than the distinct set of [P,T] pairs.

If you put it all together,

tweet( anne , tweet1 ).
tweet( anne , tweet5 ).
tweet( fred , tweet2 ).
tweet( fred , tweet7 ).
tweet( fred , tweet8 ).

tweets( P  , Ts ) :- atom(P) , ! , tweets([P],Ts) .
tweets( Ps , Ts ) :- var(Ps) , ! , setof(P,T^tweet(P,T),Ps) , tweets(Ps,Ts) .
tweets( Ps , Ts ) :- findall(T,desired_tweet(Ps,T),Ts).

desired_tweet(Ps,T) :- tweet(P,T), member(P,Ps).

You can says tweets(anne,Ts) and get the expected Ts = [tweet1, tweet5].

And, similarly, you can say tweets(Ps,Ts) and get

Ps = [anne, fred],
Ts = [tweet1, tweet5, tweet2, tweet7, tweet8]
Related