Search code examples
vb.netrecursiongeneric-function

Call a generic function passed as parameter in recursive function


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.


Solution

  • 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).