如何写一个currying Scala函数特征?

时间:2016-10-07 16:49:23

标签: scala

问题

第一种方法

如果想拥有

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 = ???
}

apply的{​​{1}}与object GaussianKernel extends Kernel override的{​​{1}}不同。

第二种方法 - 编辑: 结果证明这是后续工作......

或者我可以写

apply

但我不确定这是在干嘛......

编辑: 事实证明,我可以以一种讨人喜欢的方式使用第二种方法。我认为这正是典型的咖喱,只是没有语法糖。

想法的解释

这个想法是这样的:对于我的算法,我需要一个Kernel。该内核计算空间中两个向量的度量 - 这里是trait Kernel s。为此,内核需要一种方法来计算两个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)) } 之间的距离。距离和内核都应该是可交换的(open-closed principle),因此我将它们声明为特征(在Java中我将它们声明为接口)。在这里,我使用Euclidean Distance(未显示)和Gaussian Kernel。为什么要讨好?稍后使用这些内容时,SpacePoint对于所有测量都会或多或少相同,而SpacePoint将始终发生变化。再次,努力坚持开放封闭原则。因此,在第一步中,我希望distance预先配置(如果您愿意)一段距离并返回一个SpacePoint,该GaussianKernel可以在{{1}的程序中稍后提供我确定代码是错的,只是为了让你知道我的目标是什么:

Function

问题

  1. 我如何建立自己的特质?
  2. 由于SpacePoint对于不同的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) } s可能不同 - distance应该是一个类而不是一个对象吗?
  3. 我应该部分申请GaussianKernel而不是currying吗?
  4. 我的方法是否错误,GaussianKernel应该是一个将GaussianKernel存储在字段中的类?

2 个答案:

答案 0 :(得分:0)

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

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)

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

答案 1 :(得分:0)

答案

1。我如何建立自己的特质?

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

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)

为什么第一种方法不起作用

因为currying只适用于方法(duh ......)。问题始于一个函数非常类似于一个方法的概念,只是实际的方法是apply方法,它通过调用Function的“构造函数”来调用。

首先:如果一个对象有一个apply方法,它已具备此功能 - 无需扩展Function。扩展函数仅强制对象具有apply方法。当我在这里说“对象”时,我指的是一个单独的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方法应该是currying:

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))相同,第二种方法)。

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

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

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

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

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

一般来说,根据http://www.vasinov.com/blog/on-currying-and-partial-function-application/#toc-use-cases

,这是首选

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

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

见2。