Каков наилучший шаблон для определения параметров делегата (с использованием .NET 2.0 или более поздней версии)?
Иногда бывает полезно выполнить вызов метода с параметрами и превратить его в MethodInvoker, который будет вызывать указанную функцию с этими параметрами, не указывая при этом параметры. В других случаях полезно делать что-то подобное, но оставляя некоторые параметры открытыми. Этот тип действия называется "Currying". Каков наилучший шаблон для этого в VB?
В VB 2010 можно использовать лямбда-выражения, но лямбда-выражения несовместимы с редактированием и продолжением, а создаваемые ими замыкания могут иметь неожиданное поведение по ссылке. Альтернативный подход заключается в определении некоторых общих методов, таких как показано здесь:
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
Если я хочу создать MethodInvoker, который будет выполнять Foo (5, "Hello" ), я могу создать его с помощью
MyInvoker = NewInvoker(AddressOf Foo, 5, "Hello")
и если я хочу превратить MyAction (X) в Boz (X, "George", 9), где X - Double, я могу использовать
MyAction = ActionOf(Of Double).Create(AddressOf Boz, "George", 9)
Все довольно гладкие, за исключением того, что необходимо иметь огромное количество кода шаблона для размещения разных чисел фиксированных и нефиксированных параметров, и нет ничего, что было бы связано с синтаксисом создания делегата, который бы четко определял, какие параметры фиксированы и какие нефиксированы. Есть ли способ улучшить шаблон?
Добавление:
Каков механизм, если делегат создан из функции-члена структуры? Кажется, что делегат получает свою собственную копию структуры, но я не знаю, помещена ли эта копия или распакован. Если он не вставлен в коробку, заменив CurryAction0 и CurryAction1 на структуры, можно избежать необходимости выделять CurryAction0 или CurryAction1 в качестве отдельного объекта кучи при создании делегата. Однако, если он будет включен в коробку, использование структуры добавит накладные расходы на копирование структуры на экземпляр в штучной упаковке, не сохраняя ничего.
Ответы
Ответ 1
Если вы можете использовать .Net 4, как насчет кортежей?
''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"
Ответ 2
Это не исключает требования к шаблону для каждого Func
и любого возможного количества "поздних" аргументов, но я просто хочу показать, что "простой" подход все еще довольно чист. VB просто немного подробный, чтобы он выглядел как полезная конструкция.
Также текущее определение Curry
неявно работает без типов Of
, явно указанных в вызове: - (
EDIT: показать, что неявный параметр работает для явных переменных Func
.
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
Ответ 3
Узнайте, что делает ContinuumLinq. Он использует шаблон для автоматического создания всех функций карри.
https://github.com/ismell/Continuous-LINQ/blob/master/ContinuousLinq/Expressions/Curry.tt
что приводит к этому
https://github.com/ismell/Continuous-LINQ/blob/master/ContinuousLinq/Expressions/Curry.cs
Может быть, вы можете взять шаблон и изменить его, чтобы сгенерировать VB?
Раул
Ответ 4
Если вы спросите меня об этом для С# 4.0, я бы сказал: используйте динамический тип.
Но самое смешное, что VB всегда поддерживал динамическую типизацию, если вы включите опцию Strict Off.
Чтобы избежать чрезмерного количества кода шаблона, вы можете попытаться выяснить, возможно ли сделать перегрузку с переменным числом параметров. Он будет медленнее, но это полезная система безопасности, обеспечивающая, чтобы ваш код работал для любой функции.
Я думаю, вам, вероятно, понадобятся закрытия в качестве детали реализации, но это нормально?