Skip to content
This repository has been archived by the owner on Mar 23, 2023. It is now read-only.

Allow passing Python functions to Go for invocation. #189

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

Conversation

nairb774
Copy link
Contributor

Big WIP - This code is gnarly and rather than continuing to sit on it, thoughts on how to handle all the nasty panics below? There has to be better ways. Regardless, this opens up a lot more interoperability with the Go ecosystem - just a matter of how to handle error cases.

Copy link
Contributor

@trotterdylan trotterdylan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really cool. I like the general approach. Just a few suggestions.

continue

case reflect.Func:
if fn, ok := val.Interface().(func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException)); ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could store reflect.TypeOf(func(*Frame, Args, KWArgs) (*Object, *BaseException) {}) and compare directly before doing val.Interface(). I have a sense that'd be more efficient if this is the uncommon case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The common case should be that when value.Kind()==Func this interface assertion should pass (at least today). If other built in types were returning other func like values from their Native slot, then this might need to be adjusted.

}

func nativeToPyFuncBridge(fn Func, target reflect.Type) reflect.Value {
firstInIsFrame := target.NumIn() > 0 && target.In(0) == reflect.TypeOf((*Frame)(nil))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to cache reflect.TypeOf((*Frame)(nil)) as a global somewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


case reflect.Func:
if fn, ok := val.Interface().(func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException)); ok {
val = nativeToPyFuncBridge(Func(fn), expectedRType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Func(..) is redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of, the function type of nativeToPyFuncBridge was wrong. This was left from a bit of debugging/re-writing I did earlier.

var raised *BaseException
pyArgs[i], raised = WrapNative(frame, arg)
if raised != nil {
panic(fmt.Sprintf("WrapNative(%v)=%v", arg, raised)) // TODO: Figure this out...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any great suggestions here. Given that we can't assume anything about the nature or the context of the call, panic is probably the only general option available to us. In the specific case where the last return value is *BaseException, we could return the raised exception, but that's going to have limited applicability.

The value panicked could just be the raised exception, that way at least there's some opportunity to catch it and recover.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me.

return []reflect.Value{v}
}

// TODO: Support other iterables?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be pretty easy to accomplish with seqForEach() or similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL.

@nairb774
Copy link
Contributor Author

This still needs some tests. I'll start working on those now that this looks like it is in a better place, but feel free to review the code in the implementation.

Copy link
Contributor

@trotterdylan trotterdylan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one small suggestion in addition to your plan to add tests.

Also, something occurred to me: if *BaseException implemented the error interface, then we could also return an exception when the target function returns an error as its last result. I don't think we should do this now because it could definitely be surprising. But it's nice to have in our back pockets if it turns out to be useful and it feels natural.

}
}

var baseExceptionReflectType = reflect.TypeOf((*BaseException)(nil))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Group these globals at the top of the file please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@nairb774
Copy link
Contributor Author

PTAL.

Copy link
Contributor

@trotterdylan trotterdylan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this, it's looking really good. Just one high level question/thought and one minor nit.

continue

case reflect.Func:
if fn, ok := val.Interface().(func(*Frame, Args, KWArgs) (*Object, *BaseException)); ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This strikes me as not quite right because it only (currently) really works for function objects which return their Call method (IIUC). I wonder if we should be checking whether expectedRType.Kind() == reflect.Func upfront and if so, checking whether o.__call__ exists. If so then produce a bridge for it, otherwise, fall back to ToNative.

@@ -422,6 +422,122 @@ func TestMaybeConvertValue(t *testing.T) {
}
}

func TestNativveToPyFuncBridge(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/TestNativve/TestNative/

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.

2 participants