2

My desire is to run a given function by name through AddressOf with one input parameter, e.g. Function Foo(x as Integer) As Integer. The two inputs I need into the recursive function are the function name _name As String and an object of some type t _list As t (Integer, Double, List(Of Integer), etc). The goal is to process either an element or list of elements with the function name, as there are multiple times I need to process a list by a given function and I do not wish to replicate the list processing code in each location. The ways I've tried to call my best go at this type of function (below) that didn't crash completely resulted in this error:

Warning: List.Test operation failed. Overload resolution failed because no Public 'ProcessList' can be called with these arguments: 'Public Shared Function ProcessList(Of t)(_func As Func(Of Object,t), _list As System.Object) As IEnumerable(Of t)': Type argument inference fails for argument matching parameter '_func'.

Iterator Function ProcessList(Of t)(_func As Func(Of Object, t), _list As Object) As IEnumerable(Of t)
    If _list.GetType = GetType(List(Of t)) Then
        Yield _list.SelectMany(Function(l) ProcessList(_func, l))
    Else
        Yield _func(_list)
    End If
End Function

For reference, I found a snippet of Python code that effectively does what I need, but I'm a little rusty on translating in this direction (Python to VB.net), and I'm not as familiar with this type of programming in VB.net. The Python snippet is:

def ProcessList(_func, _list):
    return map(lambda x: ProcessList(_func, x) if type(x)==list else _func(x), _list)

Any help as to how I need to call this function, or how to rework this function if my approach is flawed, would be greatly appreciated!

Update:

I re-examined how I was calling the function and a few other things based on @djv's info that my method is working. First, due to the nature of how I'm interfacing with these functions, I have to expose the above function with:

Public Shared Function Foo(ByVal _input As Object) As Object
    Return Utilities.ProcessList(AddressOf Bar, _input)
End Function

I'm also now getting the error message:

Warning: List.Test operation failed. Unable to cast object of type 'System.Int32' to type 'System.Collections.Generic.IList`1[System.Int32]'.

The issue at this point probably lies with the method in which I'm calling my ProcessList function, rather than the function itself as I thought. I'm interfacing with a GUI that is not happy with calling ProcessList on its own, so I need this intermediate "helper" function, which I am apparently not using correctly.

pmackni
  • 307
  • 3
  • 12
  • I'm not sure what you need `ProcessList` for. Isn't [`Enumerable.Select`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select?view=netframework-4.8) exactly what you need? – Heinzi Feb 12 '20 at 19:00
  • I was under the impression I needed `ProcessList` for the recursiveness of the function, since ideally this would be replicable on any form of nested list and each element of `source` could itself be a list whose elements need to be passed `Foo`. If that's not the case, I'll look into how to make `Enumerable.Select` function in this way. – pmackni Feb 12 '20 at 19:08
  • @pmackni your provided vb.net code works using AddressOf. Do you just want to change it so you can pass a function name as string instead? – djv Feb 12 '20 at 19:23
  • @djv That would be preferable for troubleshooting purposes, but if it's working as is, I'll have to take another look at how I'm calling it and what information I'm feeding it in my tests. – pmackni Feb 12 '20 at 19:36
  • 1
    I can see a potential problem passing the function name instead of delegate in that the delegate will include the return type of your function, but the function name will not. Your `ProcessList(Of t)(_func As Func(Of Object, t)` gets the type of t from the delegate. That would require you to call it like `ProcessList(Of Integer)("foo1", i)`, defeating the purpose of generics. – djv Feb 12 '20 at 19:54
  • I realize there is a different between `Function foo1(param As Integer) As Integer` and `Function foo1(param As Object) As Integer`, and perhaps this is where your code fails. – djv Feb 12 '20 at 20:06

1 Answers1

0

You will always get an IEnumerable(Of T) and T can either be a primitive (i.e. Integer) or list of primitive (i.e. List(Of Integer)). So when you try to call it with a List, you get a List(Of List(Of Integer)) for example.

We can see why by breaking ProcessList up into two methods. The difference between them is the type of the second argument which is either T or IEnumerable(Of T)

Sub Main()
    Dim i As Integer = 1
    Dim li As New List(Of Integer) From {1, 1, 1}
    Dim ri As IEnumerable(Of Integer) = ProcessList(AddressOf foo, i).ToList()
    Dim rli As IEnumerable(Of Integer) = ProcessList(AddressOf foo, li).ToList()

    Dim d As Double = 1.0#
    Dim ld As New List(Of Double) From {1.0#, 1.0#, 1.0#}
    Dim rd As IEnumerable(Of Double) = ProcessList(AddressOf foo, d).ToList()
    Dim rld As IEnumerable(Of Double) = ProcessList(AddressOf foo, ld).ToList()

    Console.ReadLine()
End Sub

Function ProcessList(Of T)(f As Func(Of T, T), p As IEnumerable(Of T)) As IEnumerable(Of T)
    Return p.Select(Function(i) ProcessList(f, i)).SelectMany(Function(i) i)
End Function

Iterator Function ProcessList(Of T)(f As Func(Of T, T), p As T) As IEnumerable(Of T)
    Yield f(p)
End Function

Function foo(param As Integer) As Integer
    Return param + 1
End Function

Function foo(param As Double) As Double
    Return param + 1.0#
End Function

Previously, I could not even hit the line in your original code which did the SelectMany. Now, it is hit when the proper function is called. I also retooled that call to fit the new function signature.

The overloads are both called, based on the second argument passed them. However, you only need one foo method for each T (either a primitive or its IEnumerable).

djv
  • 15,168
  • 7
  • 48
  • 72
  • I've updated my op with some extra info, but it effectively boils down to I have an apparent issue with how I'm calling it. I have to wrap the original function call inside another function due to how I'm interfacing with the function itself and where I'm obtaining the source information. I'm currently still trying variations on the calling function, since I have effectively the same line as where you're calling my function, `.ToList()` and everything. – pmackni Feb 12 '20 at 20:12
  • @pmackni I only call `ToList()` to enumerate immediately. I'm afraid I don't quite understand where your code is failing :( – djv Feb 12 '20 at 20:13
  • Well, part of it was that, at some point without me realizing, Visual Studio, in all of its IDE intelligence, converted the return type of ProcessList from IEnumerable(Of t) to IEnumerable(Of List(Of t)). So that didn't help. I stepped through it a little slower by hard coding some of your lines where you were assigning things to help identify where it was going wrong, since my interface isn't giving me specific lines where it failed. I've identified nearly all my issues, with the only lingering one being an input list {1, 1, 1} for some reason translates into {{1},{1},{1}} as output. – pmackni Feb 12 '20 at 20:30
  • @pmackni there is an issue with the c# compiler which prevents the method call to be fully generic when the generic types are defined by a delegate. It appears this issue is present in vb.net as well. Here is a [github](https://github.com/dotnet/csharplang/issues/129) referencing it, also related to [this question](https://stackoverflow.com/questions/6229131/why-cant-c-sharp-infer-type-from-this-seemingly-simple-obvious-case). If this was not the case we could solve your problem more simply. – djv Feb 12 '20 at 20:44
  • I've figured out that my interface, calling foo with any input (e.g. foo(1) or foo({1, 1}), seems to be causing the function to wrap each child element in a list as it passes through, such that even `{{1}, {1, 1}}` is getting converted to `{{{1}},{{1},{1}}}`. Any ideas why that might be? – pmackni Feb 12 '20 at 21:02
  • Funnily enough, altering my `Foo` to `Foo(ByVal _input as List(Of Object))` throws an error about converting an List(Of Object) to type Integer, despite ProcessList having a means to differentiate between an Enumerable and a non-Enumerable. – pmackni Feb 12 '20 at 21:13
  • 1
    When you pass a `List(Of Integer)` in like `foo(p As Object) As List(Of Integer)` then the generic T is `List(Of Integer)` according to `ProcessList`. I think this is a shortcoming of your design. – djv Feb 12 '20 at 21:16
  • I was trying to trace through to see where the issue was, and that must be the issue. I'll see if I can rework how I'm having to interface with the function, then, and just deal with copying the list processing code per function if there's no other workaround. Thanks for all the help! – pmackni Feb 12 '20 at 21:24
  • Actually, it's occurring even when passing `foo` an `Integer`. – pmackni Feb 12 '20 at 21:26
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207706/discussion-between-djv-and-pmackni). – djv Feb 12 '20 at 21:32