如果想拥有
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
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
应该是一个类而不是一个对象吗?GaussianKernel
而不是currying吗?GaussianKernel
应该是一个将GaussianKernel
存储在字段中的类?答案 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)
第二种方法是要走的路。你不能像往常一样使用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方法的签名被认为是相同的。
两者都是有效的实施。稍后使用这些仅在new
存在或不存在时才会区分。
如果不喜欢new
,可以将伴侣对象视为工厂模式。
一般来说,根据http://www.vasinov.com/blog/on-currying-and-partial-function-application/#toc-use-cases
,这是首选 curry的一个优点是没有_: ???
缺少参数的更好的代码。
见2。