如何编写一个通用的Scala函数特性? 问题 第一种方法

如何编写一个通用的Scala函数特性? 问题 第一种方法,scala,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)) =>

如果你想

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 = ???
}
然而,
对象GaussianKernel Extendes内核的
应用
并不是
特征内核的
应用
的一个例外

第二种方法-编辑:结果表明,这毕竟是可行的。。。 或者我可以写

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))
}
但我不确定这是不是在讨好

编辑:结果表明,我可以以一种讨好的方式使用第二种方法。我认为这正是典型的咖喱,只是没有语法上的糖分


对这一想法的解释 这个想法是:对于我的算法,我需要一个。这个内核为空间中的两个向量计算一个度量——这里是
SpacePoint
s。为此,内核需要一种方法来计算两个
SpacePoint
s之间的距离。距离和内核都应该是可交换的(),因此我将它们声明为trait(在Java中,我将它们声明为接口)。这里我使用(未显示)和。为什么要咖喱?稍后当使用这些东西时,所有测量的
距离将或多或少相同,而
空间点将一直变化。再次强调,要坚持开闭原则。因此,在第一步中,我希望用距离预先配置
GaussianKernel
(如果您愿意),并返回
函数
,该函数可以稍后在程序中使用
空间点
s馈送(我确信代码是错误的,只是为了让您了解我的目标):


问题
  • 我如何建立我的特质
  • 既然不同的
    高斯角
    的距离可以不同,那么
    高斯角
    应该是一个类而不是一个对象吗
  • 我是否应该部分地应用
    GaussianKernel
    而不是curry
  • 我的方法是否不好,
    GaussianKernel
    应该是一个在字段中存储
    距离的类

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

    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)
    

    我会首先将所有东西建模为函数,然后仅在需要时将共性汇总到特征中。

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

    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.我如何建立我的特质? 第二种方法是要走的路。你不能像往常一样使用咖喱的语法,但这与咖喱是一样的:

    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)
    
    object GaussianKernel extends Kernel {
      override def apply(distance: Distance)(sp1: SpacePoint, sp2: SpacePoint): Double = ???
    }
    
    为什么第一种方法不起作用 因为咖喱只能用于方法(duh…)。问题始于这样一个概念,即函数非常像一个方法,只是实际的方法是apply方法,它是通过调用函数的“构造函数”来调用的

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

    第二:咖喱是一种语法上的糖:

    def mymethod(a: Int)(b: Double): String = ???
    
    语法上的糖是什么

    def mymethod(a: Int): ((Double) => String) = ???
    
    def mymethod(a: Int): Function1[Double, String] = ???
    
    哪个是语法糖

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

    (如果我们扩展一个函数N[T1,T2,…,Tn+1],它是这样工作的:最后一个类型Tn+1是apply方法的输出类型,前N个类型是输入类型。)

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

    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)
    
    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]]
    
    (这与距离=>((SpacePoint,SpacePoint)=>Double)相同)
    ,第二种方法)

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