【问题标题】:How to write a currying Scala Function trait?如何编写柯里化的 Scala 函数特征?
【发布时间】:2017-02-16 18:03:19
【问题描述】:

问题

第一种方法

如果想拥有

trait Distance extends ((SpacePoint, SpacePoint) => Double)

object EuclideanDistance extends Distance {
  override def apply(sp1: SpacePoint, sp2: SpacePoint): Double = ???
}

trait Kernel extends (((Distance)(SpacePoint, SpacePoint)) => Double)

object GaussianKernel extends Kernel {
  override def apply(distance: Distance)(sp1: SpacePoint, sp2: SpacePoint): Double = ???
}

但是object GaussianKernel extends Kernelapply 并不是trait Kernelapply 的例外override

第二种方法 - 编辑: 事实证明这是可行的...

或者我可以写

trait Kernel extends ((Distance) => ( (SpacePoint, SpacePoint)  => Double))

object GaussianKernel extends Kernel {
    override def apply(distance: Distance): (SpacePoint, SpacePoint) => Double =
        (sp1: SpacePoint, sp2: SpacePoint) =>
            math.exp(-math.pow(distance(sp1, sp2), 2) / (2))
}

但我不确定这是在柯里化......

编辑: 事实证明,我可以以柯里化的方式使用第二种方法。我认为这正是典型的柯里化,只是没有语法糖。


想法的解释

想法是这样的:对于我的算法,我需要一个Kernel。这个内核计算空间中两个向量的度量——这里是SpacePoints。为此,内核需要一种方法来计算两个SpacePoints 之间的距离。距离和内核都应该是可交换的(open-closed principle),因此我将它们声明为特征(在 Java 中我将它们声明为接口)。这里我使用Euclidean Distance(未显示)和Gaussian Kernel。为什么是咖喱?稍后在使用这些东西时,distance 对于所有测量值都会或多或少相同,而SpacePoints 会一直变化。再次,努力坚持开闭原则。因此,在第一步中,我希望GaussianKernel 被预先配置(如果你愿意的话)一个距离并返回一个Function 可以稍后在程序中使用SpacePoints 提供(我确定代码错误,只是为了让您了解我的目标):

val myFirstKernel  = GaussianKernel(EuclideanDistance)
val mySecondKernel = GaussianKernel(FancyDistance)
val myThirdKernel  = EpanechnikovKernel(EuclideanDistance)
// ...  lots lof code ...
val firstOtherClass  = new OtherClass(myFirstKernel)
val secondOtherClass = new OtherClass(mySecondKernel)
val thirdOtherClass  = new OtherClass(myThirdKernel)

// ...  meanwhile in "OtherClass" ...
class OtherClass(kernel: Kernel) {
    val thisSpacePoint = ??? // ... fancy stuff going on ...
    val thisSpacePoint = ??? // ... fancy stuff going on ...
    val calculatedKernel = kernel(thisSpacePoint, thatSpacePoint)
}

问题

  1. 如何建立我的特质?
  2. 由于 distance 对于不同的 GaussianKernels 可能不同 - GaussianKernel 应该是一个类而不是一个对象吗?
  3. 我应该部分应用GaussianKernel而不是currying吗?
  4. 我的方法是不是不好,GaussianKernel 应该是一个在字段中存储distance 的类?

【问题讨论】:

  • 我不明白你在做什么。为什么距离是一个特征,它扩展了一个接受一个元组并返回一个双精度的函数?你想要完成什么(你所追求的用例是什么)?
  • @BrianPendleton:有帮助吗?
  • 你不能扩展 Function1 并期望它会“知道”你想要柯里化,类型已经定义。但是你为什么不使用一种 DI 给内核一个距离函数呢?一个简单的构造函数参数可以工作。或者你也可以使用蛋糕模式。
  • @LomigMégard:Function1 的类型已经定义是什么意思?我不是通过写(((Distance)(SpacePoint, SpacePoint)) => Double)来定义类型吗?
  • @LomigMégard:使用“一种 DI 为内核提供距离函数”正是我想要做的。只是我试图 a) 不将其提供给构造函数,而是将其提供给 apply,以便稍后我可以将整个事物用作函数 b) 我试图将它与柯里化结合起来。据我了解,这和蛋糕模式(相当酷的顺便说一句)都是 DI。在coderwall.com/p/t_rapw/… 中,它们被认为同样出色,只是风格不同。

标签: scala


【解决方案1】:

我只会使用函数。所有这些额外的东西只是复杂性,使事物特征似乎并没有增加任何东西。

def euclideanDistance(p1: SpacePoint1, p1: SpacePoint1): Double = ???

class MyClass(kernel: (SpacePoint, SpacePoint) => Double) { ??? }

val myClass = new MyClass(euclideanDistance)

所以只需将内核作为一个函数传递,它会计算给定两点的距离。

我在用手机,所以无法完全检查,但这会给你一个想法。

如果您有需要,这将允许您部分应用这些功能。想象一下,你有一个基本的计算方法......

def calc(settings: Settings)(p1: SpacePoint1, p1: SpacePoint1): Double = ???

val standardCalc = calc(defaultSettings)
val customCalc = calc(customSettings)

我会先将所有内容建模为函数,然后仅在需要时将共性汇总为特征。

【讨论】:

  • 内核不计算距离。距离计算距离,内核使用它来计算内核值。所以我至少需要两个功能。我猜你写的“设置”就是我的距离。
  • 是的。对不起。设置将是您的距离。所以你可以有一个基本的高斯函数来计算,然后有你的两个版本(fancyDistance,standardDistance)。
【解决方案2】:

答案

1。如何建立我的特质?

第二种方法是要走的路。你只是不能像往常一样使用柯里化的语法糖,但这和柯里化是一样的:

GaussianKernel(ContinuousEuclideanDistance)(2, sp1, sp2)
GaussianKernel(ContinuousManhattanDistance)(2, sp1, sp2)

val eKern = GaussianKernel(ContinuousEuclideanDistance)

eKern(2, sp1, sp2)
eKern(2, sp1, sp3)

val mKern = GaussianKernel(ContinuousManhattanDistance)

mKern(2, sp1, sp2)
mKern(2, sp1, sp3)

为什么第一种方法行不通

因为柯里化只能用于方法(duh...)。问题开始于这样一个概念,即 Function 非常像一个方法,只是实际的方法是 apply 方法,它是通过调用 Function 的“构造函数”来调用的。

首先:如果一个对象有一个apply方法,它就已经有这个能力了——不需要扩展一个Function。扩展一个函数只强制对象有一个应用方法。当我在这里说“对象”时,我指的是单例 Scala 对象(标识符为 object)和实例化类。如果对象是实例化类MyClass,则调用MyClass(...) 引用构造函数(因此需要new)并且应用被屏蔽。但是,在实例化之后,我可以按照提到的方式使用生成的对象:val myClass = new MyClass(...),其中 myClass 是一个对象(类实例)。现在我可以写myClass(...),调用apply 方法。如果对象是单例对象,那么我已经有了对象,可以直接写MyObject(...)来调用apply方法。当然,一个对象(在两种意义上)没有构造函数,因此应用没有被屏蔽并且可以使用。完成后,它只是 看起来 与构造函数相同,但事实并非如此(这对您来说是 Scala 语法 - 只是因为它看起来相似,并不意味着它是相同的东西)。

其次:Currying 是语法糖:

def mymethod(a: Int)(b: Double): String = ???

是语法糖

def mymethod(a: Int): ((Double) => String) = ???

这是语法糖

def mymethod(a: Int): Function1[Double, String] = ???

因此

def mymethod(a: Int): Function1[Double, String] = {
    new Function1[Double, String] {
        def apply(Double): String = ???
    }
}

(如果我们扩展一个 FunctionN[T1, T2, ..., Tn+1] 它的工作方式如下:最后一个类型 Tn+1 是 apply 方法的输出类型,前 N 个类型是输入类型.)

现在,我们希望这里的 apply 方法应该是柯里化的:

object GaussianKernel extends Kernel {
  override def apply(distance: Distance)(sp1: SpacePoint, sp2: SpacePoint): Double = ???
}

翻译成

object GaussianKernel extends Kernel {
    def apply(distance: Distance): Function2[SpacePoint, SpacePoint, Double] = {
        new Function2[SpacePoint, SpacePoint, Double] {
            def apply(SpacePoint, SpacePoint): Double
        }
    }
}

现在,GaussianKernel 应该扩展什么(或者 GaussianKernel)?它应该扩展

Function1[Distance, Function2[SpacePoint, SpacePoint, Double]]

(与Distance => ((SpacePoint, SpacePoint) => Double))相同,第二种方式)。

现在的问题是,这不能写成柯里化,因为它是类型描述而不是方法的签名。在讨论了这一切之后,这似乎是显而易见的,但在讨论这一切之前,它可能没有。问题是,类型描述似乎直接转换为 apply 方法(第一个或唯一一个,取决于如何将语法糖分开)签名,但事实并非如此。不过公平地说,这是可以在编译器中实现的:类型描述和应用方法的签名被认为是相等的。

2。由于不同的 GaussianKernel 的距离可能不同 - GaussianKernel 应该是一个类而不是一个对象吗?

两者都是有效的实现。仅在 new 存在或不存在时才使用这些稍后区分。

如果不喜欢new,可以将伴随对象视为工厂模式。

3。我应该部分应用 GaussianKernel 而不是 currying?

根据http://www.vasinov.com/blog/on-currying-and-partial-function-application/#toc-use-cases,通常这是首选

currying 的一个优点是没有_: ??? 的缺少参数的代码更好。

4。我的方法是不是很糟糕,GaussianKernel 应该是一个在字段中存储距离的类?

见 2。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-29
    相关资源
    最近更新 更多