11

有时,进行方法调用、完成参数并将其转换为 MethodInvoker 会很有用,该方法将使用这些参数调用指定的函数,而无需当时指定参数。在其他时候,做一些类似的事情很有用,但要保留一些参数。这种类型的动作称为“Currying”。在 VB 中执行此操作的最佳模式是什么?

在 VB 2010 中可以使用 lambda 表达式,但 lambda 表达式与 edit-and-continue 不兼容,并且它们创建的闭包可能具有意外的引用行为。另一种方法是定义一些通用方法,如下所示:

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

4 回答 4

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"
于 2010-12-23T10:03:27.227 回答
0

这并不能避免每个可能数量的“迟到”参数的样板要求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
于 2011-06-20T14:56:18.953 回答
0

如果你问我这个 C# 4.0,我会说:使用动态类型。

但有趣的是,如果您关闭 Option Strict,VB 一直支持动态类型。

为了避免过多的样板代码,您可以尝试查看是否可以使用可变数量的参数进行重载。它会更慢,但它是一个有用的安全网,可确保您的代码适用于任何功能。

我认为您可能需要闭包作为实现细节,但这没关系,不是吗?

于 2012-01-22T21:27:31.553 回答
0

看看 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 ?

劳尔

于 2011-12-30T18:58:10.093 回答