I want to define an interface for any coordinate-type-thing which implements a Distance() method that can be used to calculate distance (maybe cartesian, maybe chronological, whatever) from another coordinate-type-thing of the same type.
This is the general idea:
type Distancer interface {
Distance(other interface{}) int
}
This doesn't provide type-safety for the argument to Distance(), though, which must be of the same type. For example, I might have a ParticularTime struct that implements Distance(); passing another ParticularTime object as the argument makes sense, but passing a ParticularLocation doesn't make sense at all.
I suspect this could be caught at compile-time at least in some cases, e.g. this is obviously wrong:
x := ParticularLocation{}
y := ParticularTime{}
distance := x.Distance(y)
Is there any way to express this restriction? Or do I have to do runtime type-checking inside every implementation of Distance() ?
Am I thinking about this problem the wrong way?
This is an homomorphism of the expression problem. Also relatively easy to generalize, if we assume distance to mean euclidean distance, which is a reasonable assumption for a broad range of applications. As Go does not support dependent types or anything that fancy, though, we will have to make a reasonable compromise in the amount of dimensions that we want to support, in favor of type-safety.
Let's implement this for the first 3 dimensions.
type OneVectorer interface {
Vector() [1]float64
}
type TwoVectorer interface {
Vector() [2]float64
}
type ThreeVectorer interface {
Vector() [3]float64
}
Then three type-safe methods:
func OneDimensionalDistance(a OneVectorer, b OneVectorer) float64 {
return euclideanDistance(a.Vector()[:], b.Vector()[:])
}
func TwoDimensionalDistance(a TwoVectorer, b TwoVectorer) float64 {
return euclideanDistance(a.Vector()[:], b.Vector()[:])
}
func ThreeDimensionalDistance(a ThreeVectorer, b ThreeVectorer) float64 {
return euclideanDistance(a.Vector()[:], b.Vector()[:])
}
func euclideanDistance(a, b []float64) float64 {
// invariant: a and b have same length
c := 0.0
for i, _ := range a {
c += math.Pow(b[i]-a[i], 2)
}
return math.Sqrt(c)
}
So, a point in time can be a OneVectorer, a cartesian point a TwoVectorer, and so on...
You can define further types to your convenience in order to make the program more expressive and in their methods map down to this vector arithmetic.