Skip to content
This repository has been archived by the owner on Nov 15, 2019. It is now read-only.

Olegs/expose native objects #23

Closed
wants to merge 3 commits into from

Conversation

wvxvw
Copy link
Contributor

@wvxvw wvxvw commented Mar 29, 2017

The changes roughly fall into these categories:

  • New Prolog type term.Native
    This required some (very few) modifications in term package to deal with caching of terms (perhaps it would be better to let each kind of term to implement a method which returns something that may be cached?) Another question here: I treated native nil as if it was a non-instantiated Prolog variable. It was useful in some of my code, but I'm not sure it's the correct way to go about it.

  • Serialization and deserialization of Go objects into Prolog objects
    I'm not entirely confident about correctness of all of my code. (Go reflection is a huge mess...) But for the instances I tried, it seems to work. There are some tests for decoding and encoding, but they don't cover 100% of possible cases. There are also some todos in that general area related to integer overflow that may happen when converting between these types. It wasn't a high priority problem for me, but if need be, I can fix that.

  • help and apropos predicates
    I found that for interactive use with a large database these are invaluable (I used them with my own code because I couldn't remember the exact names or the number / order of arguments). But, really this is optional feature, however it required one small change in machine struct. I also used a regular hash-table rather than persistent one because these are intended for interactive use, where concurrency isn't an issue. Again, I can redo it using the persistent hash-table to be consistent with the rest of the code.

  • Pretty printing of strings and lists
    Instead of printing strings like this '.'(23, '.'(24, ...) now it prints it "ab...". This makes the printing a little slower, but I think it's worth the effort. Especially since most printing happens interactively anyways. The printing code doesn't handle recursive terms (I'm not really sure if this is something that's even supported in Golog in general). Anyways, if need be, I can fix it to also avoid looping indefinitely over recursive structures.

@mndrix
Copy link
Owner

mndrix commented Mar 29, 2017

I finished reading all the changes. First off, I can tell that this took a lot of effort. Thanks for being willing to contribute it.

Can you isolate the pretty printing changes in one commit, the apropos/help changes in a second commit and send me a pull request for those two? If so, I should be able to merge it right away.

I love the idea of easy conversion between Go types and Prolog terms. The biggest question in my mind is how to represent a Go struct in idiomatic Prolog. Let's consider this type for conversation:

type Person struct {
    Name string
    Age int
}

The code in this pull request represents the Go value Person{Name:"Bob", Age: 27} with the following term:

person(name("Bob"),age(27))

I'm I were to represent that same value in idiomatic Prolog, without any thought for Go, I'd probably use person("Bob",27) with some accessor predicates like:

person_name(person(N,_),N).
person_age(person(_,A),A).

Losing the name wrapper around each Prolog argument makes it harder to convert from Prolog back into Go, but I think it gives us a more natural representation in Prolog.

Ideally, I think I'd like something along these lines. You may add field tags like golog:"foo,7" to assist with the conversion to/from Prolog. That uses the Prolog name foo and puts it in the 7th position in a compound term. Using the type above, and being explicit about the tags, we'd have:

type Person struct {
    Name string `golog:"name,1"`
    Age int `golog:"age,2"
}

Then something like term.NewCompound(val interface{}) *term.Compound as a convenience for converting Go values into Prolog terms. Along with term.Decode(t term.Term, dst interface{}) to converting Prolog terms into Go values. Both of these functions are probably built on top of more general Marshal and Unmarshal interfaces. Since all these functions are about Prolog terms, not their execution, I think they belong in the term package.

I'd also want to be able to do something like m := golog.NewMachine().Consult(&Person{}) to automatically add person_name/2 and person_age/2 definitions to my machine. It'd be especially cool if methods on *Person were converted into foreign predicates which called the method. Anyway, these helper predicates make it easy to work with Go values in Prolog in a natural way. If the Go types change, the Prolog helpers automatically change too so I don't have to rewrite my Prolog code.

If you're willing to refactor your native object support in this direction, I think we can get it to a point to merge as well.

@wvxvw
Copy link
Contributor Author

wvxvw commented Mar 30, 2017 via email

@ghost ghost force-pushed the olegs/expose-native-objects branch 2 times, most recently from e49f6a4 to ffa4867 Compare March 30, 2017 11:05
@ghost ghost force-pushed the olegs/expose-native-objects branch from ffa4867 to f5eb3cd Compare March 30, 2017 11:07
mndrix added a commit that referenced this pull request Mar 30, 2017
This gives the toplevel access to help and apropos predicates.

See #23
@mndrix
Copy link
Owner

mndrix commented Mar 30, 2017

I've cherry picked the help/apropos and pretty printing commits. I also pushed a follow up commit to fix test failures related to pretty printing.

It'd be nice if help(printf/1) worked so that users don't have to wrap the predicate indicator in a string. I figured that was a minor UI issue at this point, so I didn't want it to hold back the changes entirely.

I didn't actually represent Go structs like person(name("Bob"),
age(27)), it was person([name("Bob"), age(27)])

Sorry. I missed the list wrapper during my review.

do you think it
might be still better to have fields as a list rather than tuple?

I prefer to work with plain compound terms in Prolog, but SWI-Prolog uses option lists in some places. At this point, I'm not sure we know enough about how Golog users prefer to translate their Go types into Prolog values. Without that kind of feedback, I'm hesitant to merge anything yet.

@mndrix mndrix closed this Mar 30, 2017
@wvxvw
Copy link
Contributor Author

wvxvw commented Mar 31, 2017

Sorry to re-open this thread: I've went on to try to add predicates to map Go methods to Prolog, and there are several conceptual problems with that. I just wanted to leave these findings here for if this discussion will surface again later:

  • The generated name may clash with an existing predicate.
  • Machine.Consult() isn't probably the best way to do this because if a Go object implements io.Reader, Consult() will not encode it, but instead will try to read it. Also, strings can be encoded by native.Encoder too, so that would be a problem as well.
  • You may want to encode / decode Go objects of the same type more often than you'd want to create accessors for them, because what you are interested in is in getting their values into the interpreter. A simple example would be a Go slice containing multiple instances of the same kind of object--obviously, you wouldn't want to go through the same process for each element of a slice, for example.
  • Related to the bullet-point above: would it be necessary to generate accessors / other methods for types used / embedded in the Go object? If the type is embedded, it is likely to have methods with the same names as the embedding type: should these be treated like if they were the same method, or different methods?
  • Making sure that the signature of the generated predicate is called with compatible arguments is hard in a case when some arguments aren't fully instantiated. Also, related: should the presence of extra fields on Prolog side count as incompatibility with Go side?

A little more info on generating methods: it is hard to know if the method is a mutator, and if so, if it should instantiate the variables in the arguments. Suppose this example:

X = x([a(A)]), x_test(X).

Now, suppose the Go code was this:

type X struct {
    A int
}
func (x *X) Test() {
    x.A = 42
}

Or, maybe slightly different:

type X struct {
    A int
}
func (x *X) Test() {
   fmt.Printf("calling Test")
}

Thus, without knowing whether the Go code modified X, we cannot tell if in Prolog code we need to instantiate A... Or, maybe we could... but this would require of us to maintain some sort of mapping between Prolog variables and corresponding Go values, and then verifying if any of them changed after calling Go method... which is becoming very tricky...

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

Successfully merging this pull request may close these issues.

None yet

2 participants