Is it possible to call Rx extension methods with lambdas from inside an IronPython script?
Asked Answered
A

1

6

Can someone please explain me this really weird observation?

I've been trying to call Rx extension methods from inside IronPython and it is turning out to be simply impossible. I've boiled it down to this simple example:

import clr
clr.AddReference("System.Core")
from System.Linq import Enumerable

def process(value):
  return Enumerable.Select(value, lambda x:x)

In this case we start with normal LINQ. If I call the process function from my hosting environment with an array or any other IEnumerable object, it works totally fine.

So then I tried to simply replace the references to use the Observable extension methods like so:

import clr
clr.AddReference("System.Reactive.Linq")
from System.Reactive.Linq import Observable

def process(value):
  return Observable.Select(value, lambda x:x)

In this case, if I call the process function with an IObservable object, the call crashes with an ugly error message:

expected IObservable[object], got Select[int, int]

Has anyone hit upon something like this? Have I missed something? Is there some special case hack to make Enumerable work with lambda delegates that Observable is missing? I have to admit I'm completely baffled here.

By the way, just as a sanity check, the following example works just fine:

import clr
clr.AddReference("System.Reactive.Linq")
from System.Reactive.Linq import Observable

def process(value):
  return Observable.Sum(value)

I wanted to leave it out there just to make it clear that the problem really is in the method call to Observable.Select.

Ava answered 14/1, 2017 at 16:15 Comment(1)
I've filed an issue in the IronPython repository with more findings. I now believe this to be a more general bug with IronPython method call resolution, but would still appreciate more insight into the problem.Ava
M
2

Part of the problem I suspect is that the methods are overloaded. The IronPython runtime will do its best to find the best overload to use but it can get things wrong occasionally. You may have to help with the disambiguation of the overloads.

From the error message, it seems like you're trying to call it on an IObservable<int>. It seems like overload resolution is failing here and is trying to call it as Observable.Select<object, object>(). You'll need to provide some hints to what overload you want to use.

def process(value):
    return Observable.Select[int,int](value, Func[int,int](lambda x:x))
Montevideo answered 17/1, 2017 at 19:31 Comment(3)
Great, this worked and actually in this case you don't even need the explicit types on the generic method: it's enough to explicitly type the lambda. The question then is why this is not necessary with the Enumerable class, where the methods have the exact same overloads (e.g. lambdas with different argument numbers).Ava
If I had to venture a guess, the observable instance passed in implements many possibly conflicting interfaces and must have hit a case that it didn't know how to decide what type the lambda should take. Most collections are straightforward so it is easier to find the best overload. I can't verify this now but maybe there's an explicitly implemented interface that uses object somewhere and it used that.Montevideo
Actually, I'm realizing the obvious fact that IronPython cannot really infer the type of any lambda the way LINQ does, even in the case of Enumerable.Select. Given that python is an inherently dynamic language, the body of the selector could in principle return anything every time, so the only reasonable return type is object. So much for scripting LINQ this way.Ava

© 2022 - 2024 — McMap. All rights reserved.