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.
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"
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
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
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?
精彩评论