2010-12-23 8 views
10

A veces es útil tomar una llamada al método, completar con parámetros y convertirlo en un MethodInvoker que invocará la función indicada con esos parámetros, sin tener que especificar los parámetros en ese momento. En otras ocasiones, es útil hacer algo similar, pero dejando algunos parámetros abiertos. Este tipo de acción se llama "Currying". ¿Cuál es el mejor patrón para hacer esto en VB?¿Cuál es el mejor patrón para curry para delegar parámetros (usando .NET 2.0 o posterior)?

Es posible utilizar expresiones lambda en VB 2010, pero las expresiones lambda no son compatibles con edit-and-continue, y los cierres que crean pueden tener comportamientos de referencia no esperados. Un enfoque alternativo es definir algunos métodos genéricos, como se muestra aquí:

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 

Si quiero crear un MethodInvoker que llevará a cabo Foo (5, "Hola"), que puede crear una con

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

y si quiero convertir MyAction (X) en Boz (X, "George", 9), donde X es un doble, que puede utilizar

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

Todo bastante resbaladiza, excepto que es necesario tener una gran cantidad de código repetitivo para acomodar diferentes números de parámetros fijos y no fijos, y no hay nada inherente en la sintaxis de creación de delegados que aclare qué parámetros son fijos y cuáles no son fijos. ¿Hay alguna manera de mejorar el patrón?

Addendum: ¿Cuál es el mecanismo si se crea un delegado a partir de una función miembro de estructura? Parece que el delegado obtiene su propia copia de la estructura, pero no sé si esa copia está enmarcada o no. Si no está encuadrado, reemplazar CurryAction0 y CurryAction1 con estructuras evitaría la necesidad de asignar un CurryAction0 o CurryAction1 como un objeto de montón separado al crear el delegado. Sin embargo, si se va a encasillar, el uso de una estructura agregaría la sobrecarga de copiar la estructura a la instancia en caja sin guardar nada.

+0

No estoy seguro de lo que quiere, pero aprovecho la oportunidad: puede crear delegados sin especificar parámetros y luego pasarlos como Object-Array a través del método Invoke. – Bobby

+2

* 'Este tipo de acción se llama "Currying' '' - en realidad es una aplicación parcial. 'Currying' se refiere a la forma en que ciertos lenguajes estructuran las funciones para que sean fáciles de aplicar parcialmente. http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application –

+0

@Tim Robinson: Hmm ... con esa descripción, currying sería el proceso de crear el método que realiza la aplicación parcial, a diferencia del acto de realizar la aplicación en sí? En cualquier caso, he estado buscando una buena manera de hacerlo. Mi enfoque requiere mucho código repetitivo para funcionar, lo cual es molesto, pero evita las molestias de los cierres. Por cierto, ¿los delegados están en las estructuras en caja o sin caja? Si no está en la casilla, mi patrón podría mejorarse mediante el uso de estructuras en lugar de clases, pero si está en caja eso sería un desperdicio. – supercat

Respuesta

1

Si puede usar .Net 4, ¿qué hay de 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" 
+0

No es una mala idea; uno termina creando dos objetos en lugar de uno para cada delegado al curry, pero reduciría una de las "dimensiones" del número de diferentes métodos de currying. Probablemente no valga la pena usar un Tuple para 1-3 parámetros fijos, pero para más de eso, comenzaría a tener sentido. – supercat

0

Esto no evita el requisito repetitivo para cada Func y cada número posible de argumentos "tarde", pero sólo quiero mostrar el enfoque de "simple" es todavía bastante limpio. VB es un poco detallado para que parezca que es una construcción útil.

También el Curry definición actual no funciona de manera implícita y sin los Of tipos que se especifican explícitamente en la llamada :-(

EDITAR: Mostrar la opción implícita funciona para las variables explícitas 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 
.
+0

El patrón general que enumeré arriba, aunque prolijo, me sirve muy bien en algunos códigos de producción. Es un poco molesto tener que especificar explícitamente los tipos de argumentos, pero habría situaciones ambiguas si no se especificaran explícitamente. El código estándar para su enfoque que utiliza cierres es un poco más conciso; Supongo que cuando se compila termina siendo bastante similar bajo el capó. Tiendo a ser un poco cauteloso con los cierres ya que a veces pueden crear objetos redundantes o hacer que los objetos tengan una vida más larga de lo previsto, pero en esta situación ... – supercat

+0

... el objeto que el cierre va a crear va a ser esencialmente el mismo que hubiera creado de todos modos, así que no hay un problema real. A veces deseo que los delegados sean interfaces, de modo que se pueda usar una clase donde se esperaba un delegado; Delegate.Combine and Remove sería complicado bajo semejante esquema, pero considero esas cosas como horribles kludges de todos modos. – supercat

+0

@supercat: Sí, siempre puede convertir estos simples cierres de 'Función' en cierres 'manuales' con delegados, pero no creo que gane nada de eso (excepto quizás la compatibilidad 2.0). Y al releer tu pregunta, veo que eso es lo que has proporcionado. –

0

Eche un vistazo a lo que hace ContiniousLinq. Utiliza una plantilla para generar automáticamente todas las funciones de curry.

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

lo que se traduce en este

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

Tal vez usted puede tomar la plantilla y modificarla para generar algo de VB?

Raul

+0

Eso se ve conceptualmente similar a mi código anterior, excepto que mi código no requiere cierres. Supongo que en términos de IL generada, el enfoque con cierres sería bastante similar a mi código, ya que los cierres se implementan como clases creadas por el compilador. – supercat

0

Si me lo pedirías para C# 4.0, diría: utilizar el tipo de dinámica.

Pero lo curioso es que VB siempre ha sido compatible con el tipado dinámico, si desactiva Option Strict.

Para evitar cantidades excesivas de código repetitivo, podría intentar ver si es posible sobrecargar con una cantidad variable de parámetros. Será más lento, pero es una red de seguridad útil para garantizar que su código funcione para cualquier función.

Creo que probablemente necesitará cierres como detalle de implementación, pero está bien, ¿no?

+0

Realmente no me gustan los cierres implementados en C#/vb.net. Me parece irónico que Microsoft se diera cuenta de que pasar los parámetros del método por valor era un valor predeterminado superior para pasarlos por referencia (como se había hecho en QBasic y en versiones anteriores de vb) y, sin embargo, el comportamiento de cierre predeterminado es capturar por referencia . El patrón que utilicé para enlazar valores de parámetros en un delegado captura por valor. – supercat

Cuestiones relacionadas