【问题标题】:What is the best pattern to curry delegate parameters (using .NET 2.0 or later)?curry 委托参数的最佳模式是什么(使用 .NET 2.0 或更高版本)?
【发布时间】:2010-12-23 05:36:28
【问题描述】:

有时进行方法调用、完成参数并将其转换为 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 分配为单独的堆对象。但是,如果要装箱,则使用结构会增加将结构复制到装箱实例的开销,而不会保存任何内容。

【问题讨论】:

  • 我不确定您想要什么,但我先打个招呼:您可以在不指定参数的情况下创建委托,然后通过 Invoke 方法将它们作为 Object-Array 传递。
  • '这种类型的动作被称为“Currying”'——实际上它是部分应用。 “柯里化”是指某些语言构造函数以使其易于部分应用的方式。 en.wikipedia.org/wiki/…
  • @Tim Robinson: 嗯...根据那个描述,柯里化将是创建执行部分应用程序的方法的过程,与执行应用程序本身的行为不同?无论如何,我一直在寻找一种很好的方法来做到这一点。我的方法需要大量样板代码才能工作,这很烦人,但避免了闭包带来的麻烦。顺便说一句,结构上的代表是装箱还是拆箱?如果取消装箱,我的模式可以通过使用结构而不是类来改进,但如果装箱,那将是一种浪费。
  • @Bobby:在调用之前可以忽略参数类型,但我更喜欢在实际情况下允许编译时类型检查的方式来做事。这不仅可以更早地发现问题,而且还可以扩大在运行时无法完成的类型转换。
  • @MikeRosenblum:如果一个结构实现了一个接口,则在该接口类型的变量上使用的接口方法将操作并可以修改装箱实例。这种行为在 List.GetEnumerator() 返回的结构中最为明显,它实现了 IEnumerator.

标签: .net vb.net delegates partial-application


【解决方案1】:

如果可以使用.Net 4,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"

【讨论】:

  • 不错的主意;最终会为每个柯里化委托创建两个对象而不是一个,但它会减少不同柯里化方法数量的“维度”之一。可能不值得为 1-3 个固定参数使用元组,但不止于此,它会开始变得有意义。
【解决方案2】:

这并不能避免每个Func 和每个可能数量的“迟到”参数的样板要求,但我只想表明“简单”方法仍然相当干净。 VB 有点冗长,因为它看起来像是一个有用的结构。

如果没有在调用中明确指定Of 类型,当前的Curry 定义也不会隐式工作:-(

编辑:显示隐式选项确实适用于显式 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

【讨论】:

  • 我上面列出的一般模式虽然冗长,但在一些生产代码中非常适合我。必须显式指定参数类型有点麻烦,但如果没有显式指定,就会出现模棱两可的情况。使用闭包的方法的样板代码更简洁一些;我猜想在编译时它最终会在引擎盖下非常相似。我倾向于对闭包有点警惕,因为它们有时会创建冗余对象或导致对象比预期的寿命更长,但在这种情况下......
  • ...闭包要创建的对象与我本来要创建的对象基本相同,所以没有真正的问题。我有时确实希望委托是接口,这样人们就可以在需要委托的地方使用一个类;在这样的方案下,Delegate.Combine 和 Remove 会很棘手,但无论如何我认为这些东西都是可怕的组合。
  • @supercat:是的,您总是可以将这些简单的Function 闭包转换为带有代表的“手动”闭包,但我认为您不会从中获得任何收益(除了 2.0 兼容性之外)。实际上重读您的问题,我发现 是 您提供的内容。
  • 仅供参考 我无法让Curry 调用隐式工作的原因已在here 进行了讨论/解释。
【解决方案3】:

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

劳尔

【讨论】:

  • 这在概念上看起来与我上面的代码有些相似,只是我的代码不需要闭包。我猜想就生成 IL 而言,使用闭包的方法与我的代码非常相似,因为闭包是作为编译器创建的类实现的。
【解决方案4】:

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

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

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

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

【讨论】:

  • 我不太喜欢 C#/vb.net 中实现的闭包。我发现具有讽刺意味的是,微软意识到按值传递方法参数比通过引用传递它们更好(就像在 QBasic 和旧版本的 vb 中所做的那样),但默认的闭包行为是按引用捕获.我用于将参数值绑定到委托中的模式按值捕获。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-20
相关资源
最近更新 更多