Rich Oliver
Rich Oliver

Reputation: 6119

Scala: Self types in Constructors for generic classes

Here's a taste of the C# code that I'm porting into Scala. No need to worry about the details.

public class GridBase<HexT, SideT, UnitT, SegT> : IGridBase
    where HexT : Hex
    where SideT : Side
    where UnitT : Unit
    where SegT : ISeg
{
  public GridBase(Geometry<HexT, SideT, UnitT, SegT> geom, IGridBase orig)
  {
  this.geom = geom;
  }
}

public class Scen: Descrip<HexC, SideC, UnitC>, IListsGeom<HexC, SideC, UnitC>
{
    public Geometry<HexC, SideC, UnitC, ISegC> geomC;
    public override IGeom iGeom { get { return geomC; } }
    public HexCList hexCs { get; private set; }
    public override HexList<HexC> hexs { get { return hexCs; } }
    public SideCList sideCs { get; private set; }
    public override SideList<SideC> sides { get { return sideCs; } }   
    public UnitCList unitCs { get; private set; }
    public override KeyList<UnitC> units { get { return unitCs; } }
}     

As Martin Odersky has pointed out the problem with generics is that the number of type parameters references and their restraint references tend to explode. However for the GridBase class, I need to resolve the types via generics rather than by abstract types. So I want to be able to get multiple types out of one type parameter. So in Scala I create a trait for my types:

abstract class Hex(val num1: Int){} //These are declared in their own files
abstract class Side {val sideString = "This is a side"}

trait DescripTypes //separate file
{
  type HexT <: Hex
  type SideT <: Side
}

class ScenTypes extends DescripTypes //separate file
{ //This is an ex of an implemntation of the above in a different package
  type HexT = HexC
  type SideT = SideC
}

Then I create my Gridbase class using a self type

class GridBase[T <: DescripTypes](val myHex: HexT) extends DescripTypes
{//Compiler doesn't recognise the HexT type in the constructor  
  other: DescripTypes =>
  type other = T  

  var testvar = 5   //The rest does nothing at useful at the moment
  var testvar2 = "" //just for testing
  def mymethod(var1: HexT) //Compiler recognises HexT
  {
    testvar += var1.num1 //This compiles fine
  }
  def method2(var1: SideT) //Compiler recognises SideT
  {
    testvar2 = var1.sideString //This compiles fine
  }

}

For some reason I can't seem to use the type members of DescripTypes in the GridBase Class constructor, although I can use them fine inside the class body. Any help appreciated. But also is this the best way to get multiple types out of one type parameter?

Clarification: All Classes are in separate files. No inner classes here.

Upvotes: 0

Views: 673

Answers (2)

Debilski
Debilski

Reputation: 67898

It’s of course hard to guess but I think your class should maybe (you’d have to change quite a bit to make it actually work) look like this

class GridBase[T <: DescripTypes](val myHex: T#HexT) { this: T =>
  var testvar = 5
  var testvar2 = ""
  def mymethod(var1: HexT) {
    testvar += var1.num1
  }
  def method2(var1: SideT) {
    testvar2 = var1.sideString
  }
}

In your example mymethod takes any HexT whereas I think you want to restrict it to the same outer class that myHex has.

Depending on the use-case, (the self-type requires you to mix-in a sub-trait of DescripTypes), the following is more appropiate:

class GridBase[T <: DescripTypes](val myHex: T#HexT) {
  var testvar = 5
  var testvar2 = ""
  def mymethod(var1: T#HexT) {
    testvar += var1.num1
  }
  def method2(var1: T#SideT) {
    testvar2 = var1.sideString
  }
}

You’d have to specify the subclass of DescripTypes and could then use the type aliases defined in these subclasses.

val scenType = new ScenTypes
val scenTypeHex = new scenType.HexT
val scenTypeSide = new scenType.SideT
val gb_st = new GridBase[ScenTypes](scenTypeHex)
gb_st.mymethod(scenTypeHex)
gb_st.method2(scenTypeSide)

// and the same for some other subclass of DescribTypes
val scenType2 = new ScenTypes2
val scenType2Hex = new scenType2.HexT
val scenType2Side = new scenType2.SideT
val gb_st2 = new GridBase[ScenTypes2](scenType2Hex)
gb_st2.mymethod(scenType2Hex)
gb_st2.method2(scenType2Side)

// Does not compile:
gb_st2.method2(scenTypeSide)

Upvotes: 2

ron
ron

Reputation: 9396

How about T#HexT in the constructor?

# is called a type projection, and used to refer to inner types. I'm not solid in the terminology so would not go into details, but as a guideline you use A#B when performing type-level operations (see also type lambdas), while A.B is related to path-dependent types.

Note: When A is a package or object, then A.B behaves as you would expect, the # fun comes into play with traits or classes.

While not directly related, this might prove to be a good read: Independently Extensible Solutions to the Expression Problem

Upvotes: 2

Related Questions