Sunday, October 12, 2014

Go Generics Part2 - Returning Subclasses


Subclass Unions

Go can convert between different struct types if they have the same struct layout, like a union in C. Gython supports generics for methods that return different types of subclasses by generating code to recast the return type if it is different from the method's return type. This allows attributes of the returned object to be used from the caller.

Gython

class A:
 def __init__(self, x:int):
  int self.x = x
  int self.z = 0

 def bar(self) -> int:
  return self.x


class B(A):
 def __init__(self):
  A.__init__(self, 10)
  int self.z = 1

 def bar(self) ->int:
  return self.x + self.z

class C(A):
 def __init__(self):
  A.__init__(self, 100)
  int self.z = 100
  int self.w = 1

 def bar(self) ->int:
  return self.x + self.z + self.w

In the example above the subclass C extends the union by declaring a new integer variable int self.w = 1. In the generated Go code below the variable w is also inserted into the struct typedef of B. The base class A implements __object__ that contains the class name.

Go

type A struct {
 __object__
 x int
 z int
}
func (self *A)  __init__(x int) {

 self.x = x;
 self.z = 0;
}
func (self *A)  bar() int {

 return self.x
}
func __new__A( x int ) *A {
  ob := A{}
  ob.__init__(x)
  ob.__class__ = "A"
  return &ob
}
func (self *B)  A___init__(x int) {

 self.x = x;
 self.z = 0;
}
func (self *B)  A_bar() int {

 return self.x
}


type B struct {
 A
 w int
}
func (self *B)  __init__() {

 self.A___init__(10)
 self.z = 1;
}
func (self *B)  bar() int {

 return (self.x + self.z)
}
func __new__B(  ) *B {
  ob := B{}
  ob.__init__()
  ob.__class__ = "B"
  return &ob
}

func (self *C)  A___init__(x int) {

 self.x = x;
 self.z = 0;
}
func (self *C)  A_bar() int {

 return self.x
}

type C struct {
 A
 w int
}
func (self *C)  __init__() {

 self.A___init__(100)
 self.z = 100;
 self.w = 1;
}
func (self *C)  bar() int {

 return ((self.x + self.z) + self.w)
}
func __new__C(  ) *C {
  ob := C{}
  ob.__init__()
  ob.__class__ = "C"
  return &ob
}

All subclasses that inherit from a common base class are transformed to share the same attributes, they become a union. This makes the subclasses compatible with each other and allows generics by switching on their class name and type casting. This only works with attributes, but breaks with methods; to support methods a generics type switch is needed in the caller.

Subclass Switch Branching

Methods can be used on a subclass instance returned from a function or method, when the result of the call is first assigned to a variable. At this point Gython will generate a switch that checks the class name of the subclass, and then recast the variable, and enter the branch in the switch where it is that type. The function body from that point is copied into each branch in the switch. This makes the caller aware of the real subclass type of the variable, and methods can be correctly called.

Gython

class A:
 def __init__(self, x:int):
  int self.x = x
  int self.z = 0

 def some_subclass( self, o:A, s:bool ) -> self:
  if s:
   return go.type_assert(o, self)
  else:
   return self

...

def main():
 a = A( 1000 )
 b = B()
 c = C()

 ## tests returning self
 bb = b.some_subclass(b, false)
 w = bb.bar()

 cc = c.some_subclass(b, false)
 w = cc.bar()


 ## tests returning other
 ccc = b.some_subclass(c, true)
 w = ccc.bar()

 bbb = c.some_subclass(b, true)
 w = bbb.bar()


Above a method is defined in the base class some_method that returns the special type self. In its subclasses self will become the subclass type. go.type_assert(X, self) is used to cast the return type to the subclass type.

Go

The generated Go code below shows how the main function is branched for each subclass case that is possible.

func main() {

 a := __new__A(1000);
 b := __new__B();
 c := __new__C();
 __subclass__ := b.some_subclass(b, false)
 switch __subclass__.__class__ {
  case "C":
   __addr := C(*__subclass__)
   bb := &__addr
   w := bb.bar();
   __subclass__ := c.some_subclass(b, false)
   switch __subclass__.__class__ {
    case "C":
     __addr := C(*__subclass__)
     cc := &__addr
     w = cc.bar();
     __subclass__ := b.some_subclass(c, true)
     switch __subclass__.__class__ {
      case "C":
       __addr := C(*__subclass__)
       ccc := &__addr
       w = ccc.bar();
       __subclass__ := c.some_subclass(b, true)
       switch __subclass__.__class__ {
        case "C":
         __addr := C(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
        case "B":
         __addr := B(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
       }
      case "B":
       __addr := B(*__subclass__)
       ccc := &__addr
       w = ccc.bar();
       __subclass__ := c.some_subclass(b, true)
       switch __subclass__.__class__ {
        case "C":
         __addr := C(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
        case "B":
         __addr := B(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
       }
     }
    case "B":
     __addr := B(*__subclass__)
     cc := &__addr
     w = cc.bar();
     __subclass__ := b.some_subclass(c, true)
     switch __subclass__.__class__ {
      case "C":
       __addr := C(*__subclass__)
       ccc := &__addr
       w = ccc.bar();
       __subclass__ := c.some_subclass(b, true)
       switch __subclass__.__class__ {
        case "C":
         __addr := C(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
        case "B":
         __addr := B(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
       }
      case "B":
       __addr := B(*__subclass__)
       ccc := &__addr
       w = ccc.bar();
       __subclass__ := c.some_subclass(b, true)
       switch __subclass__.__class__ {
        case "C":
         __addr := C(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
        case "B":
         __addr := B(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
       }
     }
   }
  case "B":
   __addr := B(*__subclass__)
   bb := &__addr
   w := bb.bar();
   __subclass__ := c.some_subclass(b, false)
   switch __subclass__.__class__ {
    case "C":
     __addr := C(*__subclass__)
     cc := &__addr
     w = cc.bar();
     __subclass__ := b.some_subclass(c, true)
     switch __subclass__.__class__ {
      case "C":
       __addr := C(*__subclass__)
       ccc := &__addr
       w = ccc.bar();
       __subclass__ := c.some_subclass(b, true)
       switch __subclass__.__class__ {
        case "C":
         __addr := C(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
        case "B":
         __addr := B(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
       }
      case "B":
       __addr := B(*__subclass__)
       ccc := &__addr
       w = ccc.bar();
       __subclass__ := c.some_subclass(b, true)
       switch __subclass__.__class__ {
        case "C":
         __addr := C(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
        case "B":
         __addr := B(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
       }
     }
    case "B":
     __addr := B(*__subclass__)
     cc := &__addr
     w = cc.bar();
     __subclass__ := b.some_subclass(c, true)
     switch __subclass__.__class__ {
      case "C":
       __addr := C(*__subclass__)
       ccc := &__addr
       w = ccc.bar();
       __subclass__ := c.some_subclass(b, true)
       switch __subclass__.__class__ {
        case "C":
         __addr := C(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
        case "B":
         __addr := B(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
       }
      case "B":
       __addr := B(*__subclass__)
       ccc := &__addr
       w = ccc.bar();
       __subclass__ := c.some_subclass(b, true)
       switch __subclass__.__class__ {
        case "C":
         __addr := C(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
        case "B":
         __addr := B(*__subclass__)
         bbb := &__addr
         w = bbb.bar();
       }
     }
   }
 }
}

No comments:

Post a Comment