Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add session.query_all #116

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

gilbert
Copy link
Contributor

@gilbert gilbert commented Mar 29, 2020

First off, let me know if you'd like me to change anything. I am very willing to work within the needs of the project.

Motivated by #111 and access patterns for my own project, this PR adds a session.query_all method that fetches all solutions for a given query. For example:

const session = pl.create()
session.consult(`
  foo(a). foo(b). foo(c).
  bar(x). bar(b). bar(c).
`)
const solutions = await session.query_all`foo(X), bar(X).`
solutions //=> [ { X: 'b' }, { X: 'c' } ]

Notice the tagged template syntax. This allows Tau to intercept and escape values interpolated by the user:

session.consult(`
  path([one]).
  path([two, one, 'an end']).
  path([three, two, one, 'an end']).
`)
const end = ['one', 'an end']
const solutions = await session.query_all`path([X | ${end}]).`
solutions //=> [ { X: 'two' } ]

In this example, Tau escapes three values for this query. One being the end list, and the other two being its members. This is especially useful for strings with spaces, such as the an end string in our example; the user does not need to worry about wrapping anything in quotes.

Note that for queries with no variables, query_all will return an empty object for every found solution.

Big thanks to @jlturner for writing the original answer_obj function.

@triska
Copy link

triska commented Mar 29, 2020

Wow!

I only have a small question about the name: Would all_answers more clearly describe this functionality?

@jariazavalverde
Copy link
Collaborator

Hi @gilbert. First of all, thank you very much for your PR.

Tau Prolog already has a function that fetches all solutions for a given query: (Session/Thread).prototype.answers. But I see three possible contributions from this PR:

  • Make synchronous versions of answer and answers methods using promises: perhaps answerSync and answersSync (?).
  • Add a new queryTag method that supports the tagged template syntax for querying.
  • Add a new method (similar to format_answer) to easily transform solutions into JavaScript objects (the core already includes what is necessary to make these transformations).

I think it is important to separate all these aspects that you provide in your query_all function. How do you see it?

@gilbert
Copy link
Contributor Author

gilbert commented Mar 29, 2020

Thanks for the response! I don't know how I missed answers :)

  1. Could you explain why answers has a setTimeout? Since Tau is already asynchronous (I think, according to these docs), what is its purpose?

  2. answerSync/answersSync would be a little confusing since JS promises are asynchronous. Alternatively, it is a common JS library pattern to return a promise if a callback is not given. Tau could do the same thing?

  3. I'm ok with rewriting solutionToJavaScript using core functions, if that's what you mean. Which functions should I use?

  4. I agree these aspects should be provided separately, but I also believe there should be one method that does them all, like the query_all in this PR. It gives a streamlined API for a common access pattern, resulting in happier developers. I'm ok with renaming it to something else if needed.

@jariazavalverde
Copy link
Collaborator

Could you explain why answers has a setTimeout? Since Tau is already asynchronous (I think, according to these docs), what is its purpose?

The problem appears when Thread.prototype.again runs the callback defined in Thread.prototype.answers, that adds a new call to the stack. Without the setTimeout,
after running the callback, the again method doesn't leave the loop because a new call has been added. This is a problem when an asynchronous predicate slept execution for a while, because again won't stop until all choice points are consumed. In this way (with the setTimeout), the loop ends and the next call is added to the stack when JavaScript has already finished executing the again method. Then the asynchronous predicate can reactivate the thread by calling the again method when it wants.

answerSync/answersSync would be a little confusing since JS promises are asynchronous. Alternatively, it is a common JS library pattern to return a promise if a callback is not given. Tau could do the same thing?

Ok, it's a good idea!

I'm ok with rewriting solutionToJavaScript using core functions, if that's what you mean. Which functions should I use?

{Type}.prototype.toJavaScript

I agree these aspects should be provided separately, but I also believe there should be one method that does them all, like the query_all in this PR. It gives a streamlined API for a common access pattern, resulting in happier developers. I'm ok with renaming it to something else if needed.

Agree. I'll think of another name. Suggestions?

@gilbert
Copy link
Contributor Author

gilbert commented Apr 19, 2020

I'm running into a use case issue with {Type}.prototype.toJavaScript. I have the following prolog:

phase(round(1, vote)).

If I query for it like phase(P)., then P ends up being the string 'round(1, vote)' due to the way Term's toJavaScript() works.

However, my application needs the full data within phase, and not just a string representation. Something like P = ['round', [1, 'vote']]. The round here is arbitrary; other possible values could be different shapes, such as phase(setup) or phase(end).

On further thought, I might be able to use a list instead. But is it ideal to have to change my data structures to get around this limitation?

@jariazavalverde
Copy link
Collaborator

I could extend {Type}.prototype.toJavaScript to allow both formats.

@gilbert
Copy link
Contributor Author

gilbert commented May 4, 2020

Allowing both might be good, but we should probably discuss a default. I would think having data as the default makes sense. If one wanted a string representation, couldn't there be a predicate for that? e.g. phase(P), term_to_string(P, Phase)

@jariazavalverde
Copy link
Collaborator

Allowing both might be good, but we should probably discuss a default. I would think having data as the default makes sense. If one wanted a string representation, couldn't there be a predicate for that? e.g. phase(P), term_to_string(P, Phase)

I would prefer to keep the default string option for compatibility reasons.

@gilbert
Copy link
Contributor Author

gilbert commented May 22, 2020

By compatibility, do you mean with some part of the ISO standard?

@jariazavalverde
Copy link
Collaborator

No. Compatibility with old versions of Tau Prolog.

@gilbert
Copy link
Contributor Author

gilbert commented May 22, 2020

Version numbers can take care of this, and Tau is not 1.x yet. If there is a better alternative, now is the best time to consider it.

I will create a new issue for this topic since it's separate from this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants