On 24/06/2015 04:18, Marvin Humphrey wrote:
OK, it took me a bit to wrap my head around this. I think I've got it, though
I couldn't figure out how to run it. (For some reason, "go run main.go"
doesn't pick up on the clownfish.c file sitting right there and so fails with
a ton of linker errors.)
I put the three files in `$GOPATH/src/cfish_interfaces` and ran `go build
cfish-interfaces`. Not sure if that's the correct way. I don't know enough
about Go packages yet.
IMO the true alternative is to hang the itable off of the vtable; `self`
stays the same when casting, and we have to accept a penalty of either wasting
space or slower interface method dispatch.
I'd prefer to avoid this penalty. It's only needed if we want to support
Go-style implicit interfaces in Clownfish.
For type safety, should every a conversion function be autogenerated for every
declared class/interface combo? For instance, if Int declares that it
implements Comparable, this would be generated by CFC:
static inline cfish_Comparable
cfish_Int_AS_Comparable(cfish_Int *self) {
cfish_Comparable comparable
= {(cfish_Obj*)self, CFISH_INT_CFISH_COMPARABLE};
return comparable;
}
Yes, something like that. With interfaces, we have the problem that there are
two types from possibly different parcels involved. So some of the symbol
names will need two parcel prefixes. With the "short names" feature, we can
only avoid one of them.
Clownfish interface objects are converted to Go by storing a pointer to a Go
conversion function in the ITable.
Gotcha, though if I understand correctly, it looks like the first argument to
those conversion functions is an Obj*, not the two-slot interface struct.
Yes.
I think you could use `//export` and store a C function pointer in the ITable
instead.
That was my first approach. I chose to use Go function pointers because Go
functions exported with `//export` aren't called directly from C but via a
generated stub. Calling the Go function directly avoids a bit of overhead and
it doesn't require to cast the returned interface object to an unsafe.Pointer.
Also, passing raw pointers to/from C is unsafe with regard to GC. In this
case, we'd only return a pointer through the stub which should be safe. But by
calling a Go function, we can always be sure that there won't be any problems
related to GC.
I also implemented a simple object registry to pass Go objects safely to C.
Interesting that host_obj ostensibly stores a void*, but you're passing it an
integer array tick.
That's because we currently use `void*` for host objects. In Perl, it would be
an `SV*`, for example.
I thought about adding a typedef for the host object type to
`cfish_hostdefs.h`. But for Perl, this would either require to include some
Perl headers, or a forward declaration of `SV`.
The biggest problem I’m facing is the conversion of wrapped Clownfish Objs
to Clownfish interface objects on the host language side. With Go, I’m
currently using a conversion method added to each interface. This is very
efficient but it requires a bit of boilerplate for pure Go interfaces and
it’s not directly portable to dynamically typed languages. It would be great
if this could be solved on the Clownfish side. But I couldn’t find a
solution that doesn’t require an expensive lookup.
I don't quite understand. (If I thought for a while longer I probably could
figure out what you mean, but my reply has been delayed long enough.)
Consider a method with an interface type argument
Obj*
Fetch(Hash *hash, Hashable *key);
that is called from the host language:
// Go
val = hash.fetch(key)
# Perl
$val = $hash->fetch($key);
`key` could be either a Clownfish or a host language object implementing
Hashable. In the case of a Clownfish object, we need some way to lookup the
correct itable and create an interface object. For Go, I solved this with the
`UNWRAP*` methods which seems like the most efficient solution.
For Perl, we could also use such `UNWRAP*` methods. But a Perl method call is
a lot more expensive. Here it would be faster to lookup the corresponding
itable in Clownfish. For example, by adding a Hash with all implemented
interfaces to each Class.
A Clownfish function that tries to lookup an itable for any `Obj` should be
useful in some other situations. For example, it could be used to convert Go
to Clownfish interface objects with a normal function instead of a method:
func UNWRAPHashable(self Hashable) C.Hashable {
var obj Obj
var itable *C.ITable
if obj, ok := self.(Obj); ok {
// Clownfish object.
itable = C.cfish_Obj_lookup_itable(obj.TOPTR(), C.HASHABLE)
} else {
// Pure Go interface.
obj = objRegistry.Store(self)
itable = C.HASHABLE_HOSTOBJ
}
return C.Hashable{obj.TOPTR(), itable}
}
This isn't as efficient as my current approach. But we'll probably use
something like that for Perl. I'm just not sure about all the trade-offs yet.
Indeed. Each "default" method would require a concrete wrapper for each
implementing class in such languages. However, it's such a powerful feature
that I think that's probably an acceptable cost.
I agree.
Nick