11

Sometimes it is useful to take a method call, complete with parameters, and turn it into a MethodInvoker which will invoke the indicated function with those parameters, without having to specify the parameters at the time. At other times, it's useful to do something similar, but leaving some parameters open. This type of action is called "Currying". What is the best pattern for doing this in VB?

It's possible to use lambda expressions in VB 2010, but lambda expressions aren't compatible with edit-and-continue, and the closures they create can have unexpected by-reference behaviors. An alternative approach is to define some generic methods such as shown here:

Public Module CurryMagic
    Delegate Sub Action(Of T1, T2)(ByVal P1 As T1, ByVal P2 As T2)
    Delegate Sub Action(Of T1, T2, T3)(ByVal P1 As T1, ByVal P2 As T2, ByVal P3 As T3)

    Class CurriedAction0(Of FixedType1, FixedType2)
        Dim _theAction As Action(Of FixedType1, FixedType2)
        Dim _FixedVal1 As FixedType1, _FixedVal2 As FixedType2
        Sub Exec()
            _theAction(_FixedVal1, _FixedVal2)
        End Sub
        Sub New(ByVal theAction As Action(Of FixedType1, FixedType2), _
                ByVal FixedVal1 As FixedType1, ByVal FixedVal2 As FixedType2)
            _theAction = theAction
            _FixedVal1 = FixedVal1
            _FixedVal2 = FixedVal2
        End Sub
    End Class

    Class CurriedAction1(Of ArgType1, FixedType1, FixedType2)
        Dim _theAction As Action(Of ArgType1, FixedType1, FixedType2)
        Dim _FixedVal1 As FixedType1, _FixedVal2 As FixedType2
        Sub Exec(ByVal ArgVal1 As ArgType1)
            _theAction(ArgVal1, _FixedVal1, _FixedVal2)
        End Sub
        Sub New(ByVal theAction As Action(Of ArgType1, FixedType1, FixedType2), _
                ByVal FixedVal1 As FixedType1, ByVal FixedVal2 As FixedType2)
            _theAction = theAction
            _FixedVal1 = FixedVal1
            _FixedVal2 = FixedVal2
        End Sub
    End Class

    Class ActionOf(Of ArgType1)
        Shared Function Create(Of FixedType1, FixedType2)(ByVal theSub As Action(Of ArgType1, FixedType1, FixedType2), ByVal FixedVal1 As FixedType1, ByVal FixedVal2 As FixedType2) As Action(Of ArgType1)
            Return AddressOf New CurriedAction1(Of ArgType1, FixedType1, FixedType2)(theSub, FixedVal1, FixedVal2).Exec
        End Function
    End Class

    Function NewInvoker(Of FixedType1, FixedType2)(ByVal theSub As Action(Of FixedType1, FixedType2), ByVal FixedVal1 As FixedType1, ByVal FixedVal2 As FixedType2) As MethodInvoker
        Return AddressOf New CurriedAction0(Of FixedType1, FixedType2)(theSub, FixedVal1, FixedVal2).Exec
    End Function
End Module

If I want to create a MethodInvoker which will perform Foo(5, "Hello"), I can create one using

MyInvoker = NewInvoker(AddressOf Foo, 5, "Hello")

and if I want to turn MyAction(X) into Boz(X, "George", 9), where X is a Double, I can use

MyAction = ActionOf(Of Double).Create(AddressOf Boz, "George", 9)

All pretty slick, except that it's necessary to have a huge amount of boilerplate code to accommodate different numbers of fixed and non-fixed parameters, and there's nothing inherent in the delegate-creation syntax which makes clear which parameters are fixed and which are non-fixed. Is there a way to improve the pattern?

Addendum: What is the mechanism if a delegate is created from a struct member function? It appears the delegate gets its own copy of the struct, but I don't know whether that copy is boxed or unboxed. If it's not boxed, replacing CurryAction0 and CurryAction1 with structs would avoid the need to allocate a CurryAction0 or CurryAction1 as a separate heap object when creating the delegate. If it's going to be boxed, though, using a struct would add the overhead of copying the struct to the boxed instance while not saving anything.

Mark Hurd
  • 10,665
  • 10
  • 68
  • 101
supercat
  • 77,689
  • 9
  • 166
  • 211
  • I'm not sure what you want, but I take a shot in the blue: You can create delegates without specifying parameters, and then passing them as Object-Array via the Invoke method. – Bobby Dec 23 '10 at 08:21
  • 2
    *'This type of action is called "Currying"'* - actually it's partial application. 'Currying' refers to the way certain languages structure functions to make them easy to partially apply. http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application – Tim Robinson Dec 23 '10 at 10:07
  • @Tim Robinson: Hmm... by that description, currying would be the process of creating the method which performs the partial application, as distinct from the act of performing the application itself? In any case, I've been searching for a nice way to do it. My approach requires a lot of boilerplate code to work, which is annoying, but avoids the nuisances of closures. BTW, are delegates on structures boxed or unboxed? If unboxed, my pattern could be improved by using structs rather than classes, but if boxed that would be a waste. – supercat Dec 23 '10 at 16:04
  • @Bobby: It's possible to ignore argument types until invocation, but I much prefer to do things in a way that allows compile-time type checking when practical. Not only does this allow earlier detection of problems, but it also allows widening type conversions to be performed which could not be done at run-time. – supercat Dec 23 '10 at 17:05
  • "BTW, are delegates on structures boxed or unboxed?" <-- The System.Delegate.Target property is typed as System.Object, so it would be boxed. Although, it is *possible* that when the custom delegate matching your signature is constructed by the compiler, that something strong-typed is going on "under the covers". I tend to doubt it, but it would certainly be possible. You might try looking at it in a Reflector or ILDSAM. – Mike Rosenblum Apr 06 '11 at 19:47
  • @Mike Rosenblum: Since asking the question, I've done some experimentation, and the act of creating a delegate boxes the structure. If the method in question modifies the structure, such modifications will be private to the copy of the structure that was created when the delegate was created. The delegate may thus behave like a mutable object (e.g. if such a delegate which is designed to do something the first three times it's invoked is added to two multicast delegates, the same mutable counter will be shared by both). – supercat Apr 06 '11 at 20:28
  • @Mike Rosenblum: Such behavior makes sense, but represents "yet another" scenario where struct-mutating methods cause problems (it's too bad there's no special declaration for struct mutating methods, since such a declaration would allow compiler warnings when boxing changes the semantics of code). – supercat Apr 06 '11 at 20:33
  • Ah, excellent observations, supercat, very cool. Not sure how to manipulate a boxed value type, however, other than reflection code, I guess. But still, *very* interesting points. – Mike Rosenblum Apr 08 '11 at 01:43
  • 1
    @MikeRosenblum: If a struct implements an interface, interface methods used upon a variable of that interface type will operate on, and can modify, the boxed instance. This behavior is most noticeable with the struct returned by List.GetEnumerator(), which implements IEnumerator. – supercat Jan 03 '12 at 16:17

4 Answers4

1

If you can use .Net 4, how about tuples?

    ''Create new tuple instance with two items.
    Dim tuple As Tuple(Of Integer, String) = _
        New Tuple(Of Integer, String)(5, "Hello")
    ''Now you only have one argument to curry, packaging both parameters
    ''Access the parameters like this (strongly typed)
    Debug.Print tuple.Item1 '' 5
    Debug.Print tuple.Item2 '' "Hello"
MarkJ
  • 30,070
  • 5
  • 68
  • 111
  • Not a bad notion; one ends up creating two objects rather than one for each curried delegate, but it would reduce one of the "dimensions" of the number of different currying methods. Probably not worth using a Tuple for 1-3 fixed parameters, but for more than that it would start to make sense. – supercat Dec 23 '10 at 12:52
0

This doesn't avoid the boilerplate requirement for every Func and every possible number of "late" arguments, but I just want to show the "simple" approach is still fairly clean. VB's just a bit verbose for it to seem like it is a useful construct.

Also the current Curry definition doesn't implicitly work without the Of types being explicitly specified in the call :-(

EDIT: Show the implicit option does work for explicit Func variables.

 Option Explicit On 
 Option Strict On
 Option Infer On

 Imports System
 Imports Microsoft.VisualBasic

 Module CurryTest

 Function Test1(ByVal X As String, ByVal Y As String) As String
   Return X & Y
 End Function

 Function Test2(ByVal X As Integer, ByVal Y As Integer) As Integer
   Return X + Y
 End Function

 Function Test3(ByVal X As Integer, ByVal Y As Integer, ByVal Z As String) As String
   Return Z & ":" & CStr(X + Y)
 End Function

 Sub Main()

   Dim Curry1 = Curry(Of String, String, String)(AddressOf Test1, "a")
   Dim Curry2 = Curry(Of Integer, Integer, Integer)(AddressOf Test2, 2)
   Dim Curry3 = Curry(Of Integer, Integer, String, String)(AddressOf Test3, 1, 2)

   Dim f As Func(Of String, String, String) = AddressOf Test1
   Dim g As Func(Of Integer, Integer, Integer) = AddressOf Test2
   Dim h As Func(Of Integer, Integer, String, String) = AddressOf Test3

   Dim Curry4 = Curry(f, "b")
   Dim Curry5 = Curry(g, 3)
   Dim Curry6 = Curry(h, 4, 5)

   Console.WriteLine(Curry1("b"))
   Console.WriteLine(Curry1("c"))

   Console.WriteLine(Curry2(2))
   Console.WriteLine(Curry2(3))

   Console.WriteLine(Curry3("Three"))
   Console.WriteLine(Curry3("3 "))

   Console.WriteLine(Curry4("c"))
   Console.WriteLine(Curry4("d"))

   Console.WriteLine(Curry5(4))
   Console.WriteLine(Curry5(5))

   Console.WriteLine(Curry6("Nine"))
   Console.WriteLine(Curry6("9 "))

 End Sub

 Function Curry(Of T, U, V)(ByVal Fn As Func(Of T, U, V), ByVal Arg As T) As Func(Of U, V)
   Return Function(Arg2 As U)(Fn(Arg,Arg2))
 End Function

 Function Curry(Of T, U, V, W)(ByVal Fn As Func(Of T, U, V, W), ByVal Arg1 As T, ByVal Arg2 As U) As Func(Of V, W)
   Return Function(Arg3 As V)(Fn(Arg1,Arg2,Arg3))
 End Function

 End Module
Mark Hurd
  • 10,665
  • 10
  • 68
  • 101
  • The general pattern I listed above, while verbose, serves me quite nicely in some production code. It's a little bit of a nuisance having to explicitly specify the argument types, but there would be ambiguous situations were they not explicitly specified. The boilerplate code for your approach using closures is a little more concise; I would guess when compiled it ends up being pretty similar under the hood. I tend to be a little wary of closures since they can sometimes create redundant objects or cause objects to be longer-lived than intended, but in this situation... – supercat Jun 20 '11 at 15:15
  • ...the object the closure is going to create is going to be essentially the same as the one I would have created anyway, so there's no real problem. I do sometimes wish delegates were interfaces, so that one could use a class where a delegate was expected; Delegate.Combine and Remove would be tricky under such a scheme, but I regard those things as horrible kludges anyway. – supercat Jun 20 '11 at 15:17
  • @supercat: Yes, you can always convert these simple `Function` closures into "manual" closures with delegates, but I don't think you gain anything from it (except perhaps 2.0 compatibility). And actually rereading your question, I see that _is_ what you've provided. – Mark Hurd Jun 20 '11 at 15:42
  • FYI The reason why I can't get the `Curry` calls to work implicitly is discussed/explained [here](http://stackoverflow.com/questions/407983/c-3-0-generic-type-inference-passing-a-delegate-as-a-function-parameter). – Mark Hurd Jul 04 '11 at 16:35
0

Check out what ContiniousLinq does. It uses a template to auto generate all the curry functions.

https://github.com/ismell/Continuous-LINQ/blob/master/ContinuousLinq/Expressions/Curry.tt

which results in this

https://github.com/ismell/Continuous-LINQ/blob/master/ContinuousLinq/Expressions/Curry.cs

Maybe you can take the template and modify it to generate some VB ?

Raul

HaxElit
  • 3,983
  • 7
  • 34
  • 43
  • That looks somewhat similar conceptually to my code above, except that my code doesn't require closures. I would guess that in terms of generated IL the approach with closures would be pretty similar to my code, since closures are implemented as compiler-created classes. – supercat Jan 03 '12 at 18:09
0

If you would ask me this for C# 4.0, I would say: use the dynamic type.

But the funny thing is that VB has always had support for dynamic typing, if you turn Option Strict Off.

To avoid excessive amounts of boilerplate code, you could try to see if it is possible to make an overload with a variable number of parameters. It will be slower but it is a useful safety net to ensure your code works for any function.

I think you will probably need closures as an implementation detail, but that's OK isn't it?

  • 1
    I don't really like closures as implemented in C#/vb.net. I find it ironic that Microsoft realized that passing method parameters by value was a superior default to passing them by reference (as had been done in QBasic as well as older versions of vb), and yet the default closure behavior is capture-by-reference. The pattern I used for binding parameter values into a delegate captures by value. – supercat Jan 22 '12 at 22:40