1

Issue

First approach

If would like to have

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

However the apply of object GaussianKernel extends Kernel is not an excepted override to the apply of trait Kernel.

Second approach - EDIT: turns out this works afterall...

Alternatively I could write

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))
}

but am not sure this is currying...

EDIT: Turns out that I can use this second approach in a currying fashion. I think it is exactly what the typical currying is, only without the syntactic sugar.


Explanation of the idea

The idea is this: For my algorithm I need a Kernel. This kernel calculates a metric for two vectors in space - here SpacePoints. For that the Kernel requires a way to calculate the distance between the two SpacePoints. Both distance and kernel should be exchangeable (open-closed principle), thus I declare them as traits (in Java I had them declared as interfaces). Here I use the Euclidean Distance (not shown) and the Gaussian Kernel. Why the currying? Later when using those things, the distance is going to be more or less the same for all measurements, while the SpacePoints will change all the time. Again, trying to stay true to the open-closed principle. Thus, in a first step I would like the GaussianKernel to be pre-configured (if you will) with a distance and return a Function that can be feed later in the program with the SpacePoints (I am sure the code is wrong, just to give you an idea what I am aiming at):

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)
}

Questions

  1. How do I build my trait?
  2. Since distance can be different for different GaussianKernels - should GaussianKernel be a class instead of an object?
  3. Should I partially apply GaussianKernel instead of currying?
  4. Is my approach bad and GaussianKernel should be a class that stores the distance in a field?
Make42
  • 12,236
  • 24
  • 79
  • 155
  • I don't understand what you're trying to do. Why is Distance a trait that extends a function which takes a tuple and returns a double? What are you trying to accomplish (what are the use cases you're after)? – Brian Pendleton Oct 07 '16 at 19:28
  • @BrianPendleton: Did that help? – Make42 Oct 08 '16 at 07:37
  • You can't extend Function1 and expect it will "know" you want currying, the types are already defined. But why don't you use a kind of DI to give a Distance Function to the Kernel? A simple constructor argument could work. Or you could also use the Cake Pattern. – Lomig Mégard Oct 08 '16 at 10:36
  • @LomigMégard: What do you mean `Function1`'s types are already defined? Am I not defining the types by writing `(((Distance)(SpacePoint, SpacePoint)) => Double)`? – Make42 Oct 08 '16 at 13:15
  • @LomigMégard: Using a "kind of DI to give a distance function to the kernel" is exactly what I am trying to do. Only that I am trying to a) not give it to the constructor, but to `apply` so that I can use the whole thing as a Function later and b) I am trying to combine it with currying. That and the Cake Pattern (pretty cool btw) are both DI, as I understand it. In https://coderwall.com/p/t_rapw/cake-pattern-in-scala-self-type-annotations-explicitly-typed-self-references-explained they are considered equally good, just different styles. – Make42 Oct 08 '16 at 13:15
  • @LomigMégard: Despite what I just wrote: I learned that composition is to be preferred over inheritance, because it is easier to reason about it (and I agree). My approach is based on composition, while the Cake Pattern is based on inheritance. – Make42 Oct 08 '16 at 13:24
  • How about `Kernel` doesn't extend a `Function` trait and you write your apply method the way you want? – Jasper-M Oct 08 '16 at 14:01
  • @Jasper-M: Turns out I did it... see my edit. – Make42 Oct 08 '16 at 14:27

2 Answers2

0

I would just use functions. All this extra stuff is just complexity and making things traits doesn't seem to add anything.

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

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

val myClass = new MyClass(euclideanDistance)

So just pass the kernel as a function that will computer your distance given two points.

I'm on my phone, so can't fully check, but this will give you an idea.

This will allow you to partially apply the functions if you have the need. Imagine you have a base calculate method...

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

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

I would start with modeling everything as functions first, then roll up commonality into traits only if needed.

Brian Pendleton
  • 839
  • 4
  • 13
  • The kernel does not compute a distance. The distance computes the distance and the kernel uses this to calculate the kernel value. So I do at least need two functions. What you write as "settings" would be my distance, I guess. – Make42 Oct 08 '16 at 11:10
  • Yes. Sorry. The settings would be your distance. So you can have a base Gaussian function to calculate, and then have your two versions of that (fancyDistance, standardDistance). – Brian Pendleton Oct 08 '16 at 11:18
0

Answers

1. How do I build my trait?

The second approach is the way to go. You just can't use the syntactic sugar of currying as usual, but this is the same as 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)

Why the first approach does not work

Because currying is only possible for methods (duh...). The issue starts with the notion that a Function is very much like a method, only that the actual method is the apply method, which is invoked by calling the Function's "constructor".

First of all: If an object has an apply method, it already has this ability - no need to extend a Function. Extending a Function only forces the object to have an apply method. When I say "object" here I mean both, a singleton Scala object (with the identifier object) and a instantiated class. If the object is a instantiated class MyClass, then the call MyClass(...) refers to the constructor (thus a new before that is required) and the apply is masked. However, after the instantiation, I can use the resulting object in the way mentioned: val myClass = new MyClass(...), where myClass is an object (a class instance). Now I can write myClass(...), calling the apply method. If the object is a singleton object, then I already have an object and can directly write MyObject(...) to call the apply method. Of course an object (in both senses) does not have a constructor and thus the apply is not masked and can be used. When this is done, it just looks the same way as a constructor, but it isn't (that's Scala syntax for you - just because it looks similar, doesn't mean it's the same thing).

Second of all: Currying is syntactic sugar:

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

is syntactic sugar for

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

which is syntactic sugar for

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

thus

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

(If we extend a FunctionN[T1, T2, ..., Tn+1] it works like this: The last type Tn+1 is the output type of the apply method, the first N types are the input types.)

Now, we want the apply method here is supposed to be currying:

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

which translates to

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

Now, so what should GaussianKernel extend (or what is GaussianKernel)? It should extend

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

(which is the same as Distance => ((SpacePoint, SpacePoint) => Double)), the second approach).

Now the issue here is, that this cannot be written as currying, because it is a type description and not a method's signature. After discussing all this, this seems obvious, but before discussion all this, it might not have. The thing is, that the type description seemed to have a direct translation into the apply method's (the first, or only one, depending on how one takes the syntactic sugar apart) signature, but it doesn't. To be fair though, it is something that could have been implemented in the compiler: That the type description and the apply method's signature are recognized to be equal.

2. Since distance can be different for different GaussianKernels - should GaussianKernel be a class instead of an object?

Both are valid implementation. Using those later only differenciates only in the presence or absence of new.

If one does not like the new one can consider a companion object as a Factory pattern.

3. Should I partially apply GaussianKernel instead of currying?

In general this is preferred according to http://www.vasinov.com/blog/on-currying-and-partial-function-application/#toc-use-cases

An advantage of currying would be the nicer code without _: ??? for the missing parameters.

4. Is my approach bad and GaussianKernel should be a class that stores the distance in a field?

see 2.

Make42
  • 12,236
  • 24
  • 79
  • 155