Каков наилучший шаблон для параметров делегирования карри (используя .NET 2.0 или более позднюю версию)? - PullRequest
11 голосов
/ 23 декабря 2010

Иногда полезно взять вызов метода, дополненный параметрами, и превратить его в MethodInvoker, который вызовет указанную функцию с этими параметрами, без необходимости указывать параметры в данный момент. В других случаях полезно сделать что-то подобное, но оставить некоторые параметры открытыми. Этот тип действия называется "Карри". Каков наилучший шаблон для этого в 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 в качестве отдельного объекта кучи при создании делегата. Однако, если он будет упакован, использование структуры добавит накладные расходы на копирование структуры в упакованный экземпляр, не сохраняя ничего.

Ответы [ 4 ]

1 голос
/ 23 декабря 2010

Если вы можете использовать .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"
0 голосов
/ 23 января 2012

Если бы вы спросили меня об этом для C # 4.0, я бы сказал: используйте динамический тип.

Но забавно то, что в VB всегда была поддержка динамической типизации, если вы выключили Option Strict.

Чтобы избежать чрезмерного количества стандартного кода, вы можете попытаться выяснить, возможна ли перегрузка с переменным числом параметров.Это будет медленнее, но это полезная сеть безопасности, гарантирующая, что ваш код работает для любой функции.

Я думаю, вам, вероятно, понадобятся замыкания в качестве детали реализации, но это нормально, не так ли?

0 голосов
/ 30 декабря 2011

Проверьте, что делает ContiniousLinq. Он использует шаблон для автоматической генерации всех функций карри.

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?

Raul

0 голосов
/ 20 июня 2011

Это не исключает стандартного требования для каждого Func и каждого возможного числа «поздних» аргументов, но я просто хочу показать, что «простой» подход все еще довольно чист. VB немного многословен, потому что кажется, что это полезная конструкция.

Также текущее определение Curry неявно не работает без явного указания типов Of в вызове: - (

РЕДАКТИРОВАТЬ: Показать неявный параметр работает для явных 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
...