2

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?

6
  • 1
    If there is no crossover between implementations, why not make them separate interfaces, like TimeDistance, LocationDistance? Commented Aug 3, 2016 at 17:47
  • This is a contrived task given to me for learning. The requirement is that another part of the code accept any abstract "coordinate" type thing. Looking at how we're going to use it, the implementations will overlap on this Distance() method. Commented Aug 3, 2016 at 19:16
  • I think you want Distance(other Distancer) int not that this helps the original question Commented Aug 3, 2016 at 19:44
  • 2
    I would say that's the incorrect interface definition if it doesn't apply to the correct set of types, and narrowing the scope of the interface is the only way to avoid type checking at runtime. Commented Aug 3, 2016 at 19:51
  • Can you suggest a better definition? Commented Aug 3, 2016 at 20:06

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.