Structural typification and polymorphism in Go - Writing a method that can work on two types having the same fields

I started learning Go after playing with structural typing in other languages ​​such as Scala and OCaml, and I'm trying to map some idiomatic methods between languages. Consider the following types

type CoordinatePoint struct { x int y int // Other methods and fields that aren't relevant } type CartesianPoint struct { x int y int // Other methods and fields that aren't relevant } 

Say we would like to write a method that works on both of these types to compute their representations of polar coordinates, func ConvertXYToPolar(point XYPoint) PolarPoint . If the CartesianPoint and CoordinatePoint types define getter and setter methods for x and y fields, we can define XYPoint as a common interface with these methods, which allows us to work on both types, but since these are stands, interfaces cannot declare fields, only methods.

Based on this, I have a few questions:

  • What is the idiomatic way to handle this in Go?
  • Can this be done without changing existing types?
  • Can we maintain type safety, i.e. to avoid defining ConvertXYToPolar without using an empty interface type as a parameter and manual conversion?
  • If interfaces and implicit interface satisfaction are the primary tools for polymorphism in Go, is the restriction of fields in interface definitions limited?
  • Are getter / setter methods commonly defined on structures to circumvent this limitation?
  • Is there a good reason why a design solution does not support fields in interface definitions?

I believe that the simplicity of built-in types, implicit satisfaction of interfaces, and interface-based polymorphism are a very simple and attractive combination of methods for increasing code reuse and maintainability, but disabling fields in interface definitions makes Go's structural typing somewhat limited from my point of view. Am I missing a simple solution?

+4
source share
3 answers

The usual way is to use a composition:

 type Point struct { x int y int } type CoordinatePoint struct { Point other stuff } type CartesianPoint struct { Point Other methods and fields that aren't relevant } 
Syntax

Go makes this composition more like inheritance in other languages. You can, for example, do this:

 cp := CoordinatePoint{} cp.x = 3 log.Println(cp.x) 

And you can call functions with the Point as parameter with

 doAThingWithAPoint(cp.Point) 

For your points to be transferred interchangeably, you will need to define an interface

 type Pointer interface { GetPoint() *Point } func (cp CoordinatePoint) GetPoint() *Point { return &cp.Point } 

Then you can define functions using Pointer :

 func doSomethingWith(p Pointer) { log.Println(p.GetPoint()) } 

Another solution will be based on an interface defining GetX , SetX , GetY and SetY , but I personally find this approach much more difficult and more detailed than necessary.

+7
source

My first project will look like this:

 package points type XYPoint struct { X, Y int64 } type CoordinatePoint struct { XYPoint } type CartesianPoint struct { XYPoint } type PolarPoint struct { R, T float64 } type XYToPolarConverter interface { ConvertXYToPolar(point XYPoint) PolarPoint } func (cp *CoordinatePoint) ConvertXYToPolar(point XYPoint) PolarPoint { pp := PolarPoint{} // ... return pp } func (cp *CartesianPoint) ConvertXYToPolar(point XYPoint) PolarPoint { pp := PolarPoint{} // ... return pp } 
+1
source
  • As a rule, the idiomatic way is to use getters and setters. Less convenient? May be. But, as it was done now, at least.
  • Yes. This is the essence of duck printing. Any type matching the interface will be accepted without the need to explicitly implement it. EDIT: Based on the comments on this answer, I misinterpreted this question. The answer is no, you will need to add methods for these structures so that they correspond to an interface other than interface{} .
  • Yes, using getters and setters.
  • May be. I see why getters and setters may be perceived as less convenient. But they do not limit what you can do as far as I can judge.
  • Yes. So I saw this in the code of others and in standard libraries.
+1
source

All Articles