(Scala 2.11)
Hello,
Overview
I'm trying to create a library which has an expandable type system. Because my library is too large and complicated, I created the following example which is completely different but has similar inheritance and dependency structure.
I created a 'cannibals' library. Its purpose is to construct cannibals belonging to different tribes (types) with the following properties:
- Works All cannibals have common actions.
- Works Each tribe has its own unique possible actions which can happen with/without another cannibal.
- Works All cannibals must belong to a tribe. A cannibal can be either 'Normal' or 'Hungry'. Hungry cannibals have extra/other actions than normal cannibals.
- Works All cannibals can produce children. A child will maintain the tribe (type) of the cannibal who produced it. A normal cannibal will produce a normal child. A hungry cannibal will produce a hungry child.
- Does not work A cannibal can convert to another tribe (but must maintain its hungry/normal status).
- Does not work An adopting tribe cannibal can adopt hungry cannibals from other tribes. An adopted cannibal maintains its tribal type. If a hungry cannibal is adopted by a hungry cannibal, then the adopted cannibal remains hungry. If it is adopted by a normal cannibal, then the adopted will become a normal cannibal.
The library has three traits (AnyCannibalLike, AnyNormalCannibalLike, AnyHungryCannibalLike) which all other traits and classes will inherit from. The library also has abstract classes used to create the concrete case classes of the different tribes. Please look at the following image to understand the inheritance structure.
Library code + JungleCannibals and UrbanCannibals examples
/////////////////////////////////////////////////////////////////////////////////////
// Any cannibal traits and abstract classes
/////////////////////////////////////////////////////////////////////////////////////
trait AnyCannibalLike {
type TBasic <: AnyCannibalLike
type TNormal <: AnyCannibalLike
type THungry <: AnyCannibalLike
type TChild <: AnyCannibalLike
//name of the cannibal
protected val _name : String
def getName() = _name
//Any cannibal can play with any cannibal
def playWith(anyCannibal : AnyCannibalLike) : Unit
//Any cannibal can bring a child of the same tribe into the world.
//If the cannibal is normal then the child will be normal
//If the cannibal is hungry then the child will be hungry
def birthChild(childName : String) : TChild
protected final def completeNameOfChild(childName : String) = childName + " (child of " + getName() + ")"
}
trait AnyNormalCannibalLike[Basic <: AnyCannibalLike] extends AnyCannibalLike {
type TBasic = Basic
type TChild = TNormal
//How to do this?
//def changeTo[Basic <: AnyCannibalLike, Normal <: AnyNormalCannibalLike[Basic], Hungry <: AnyHungryCannibalLike[Basic]]() : Normal
}
trait AnyHungryCannibalLike[Basic <: AnyCannibalLike] extends AnyCannibalLike {
type TBasic = Basic
type TChild = THungry
//Any hungry cannibal can eat other cannibals of the same tribe
def eat(sameCannibal : Basic) : Unit
//How to do this?
//def changeTo[Basic <: AnyCannibalLike, Normal <: AnyNormalCannibalLike[Basic], Hungry <: AnyHungryCannibalLike[Basic]]() : Hungry
}
abstract class AnyNormalCannibalA[Basic <: AnyCannibalLike, Normal <: AnyNormalCannibalLike[Basic], Hungry <: AnyHungryCannibalLike[Basic]](name : String) extends AnyNormalCannibalLike[Basic] {
type TNormal = Normal
type THungry = Hungry
protected val _name : String = name
def playWith(anyCannibal : AnyCannibalLike) : Unit = {
println(getName() + " playing with " + anyCannibal.getName())
}
}
abstract class AnyHungryCannibalA[Basic <: AnyCannibalLike, Normal <: AnyNormalCannibalLike[Basic], Hungry <: AnyHungryCannibalLike[Basic]](name : String, eatingTool : String) extends AnyHungryCannibalLike[Basic] {
type TNormal = Normal
type THungry = Hungry
protected val _name : String = name
def playWith(anyCannibal : AnyCannibalLike) : Unit = {
println(getName() + " playing hungrily with " + anyCannibal.getName())
}
def eat(sameCannibal : Basic) : Unit = {
println(getName() + " eating " + sameCannibal.asInstanceOf[AnyCannibalLike].getName() + " using " + eatingTool)
}
}
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// Jungle cannibal traits and case classes
/////////////////////////////////////////////////////////////////////////////////////
trait JungleCannibalLike extends AnyCannibalLike {
//Jungle cannibals can hunt with other jungle cannibals
def huntWith(jungleCannibal : JungleCannibalLike) : Unit = {
println(getName() + " hunting with " + jungleCannibal.getName())
}
}
trait JungleNormalCannibalLike extends AnyNormalCannibalLike[JungleCannibalLike] with JungleCannibalLike
trait JungleHungryCannibalLike extends AnyHungryCannibalLike[JungleCannibalLike] with JungleCannibalLike {
//Jungle cannibals can hunt with and then eat other jungle cannibals
def huntAndEat(jungleCannibal: JungleCannibalLike) : Unit = {
huntWith(jungleCannibal)
eat(jungleCannibal)
}
}
case class JungleNormalCannibal(name : String) extends AnyNormalCannibalA[JungleCannibalLike, JungleNormalCannibalLike, JungleHungryCannibalLike](name) with JungleNormalCannibalLike {
def birthChild(childName : String) : TChild = JungleNormalCannibal(completeNameOfChild(childName))
}
case class JungleHungryCannibal(name : String) extends AnyHungryCannibalA[JungleCannibalLike, JungleNormalCannibalLike, JungleHungryCannibalLike](name,"hands") with JungleHungryCannibalLike {
def birthChild(childName : String) : TChild = JungleHungryCannibal(completeNameOfChild(childName))
}
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// Urban cannibal traits and case classes
/////////////////////////////////////////////////////////////////////////////////////
trait UrbanCannibalLike extends AnyCannibalLike {
//Urban cannibals can read with other urban cannibals
def readWith(urbanCannibal : UrbanCannibalLike) : Unit = {
println(getName() + " reading with " + urbanCannibal.getName())
}
}
trait UrbanNormalCannibalLike extends AnyNormalCannibalLike[UrbanCannibalLike] with UrbanCannibalLike
trait UrbanHungryCannibalLike extends AnyHungryCannibalLike[UrbanCannibalLike] with UrbanCannibalLike {
//Urban cannibals can read with and then eat other urban cannibals
def readAndEat(urbanCannibal: UrbanCannibalLike) : Unit = {
readWith(urbanCannibal)
eat(urbanCannibal)
}
}
case class UrbanNormalCannibal(name : String) extends AnyNormalCannibalA[UrbanCannibalLike, UrbanNormalCannibalLike, UrbanHungryCannibalLike](name) with UrbanNormalCannibalLike {
def birthChild(childName : String) : TChild = UrbanNormalCannibal(completeNameOfChild(childName))
}
case class UrbanHungryCannibal(name : String) extends AnyHungryCannibalA[UrbanCannibalLike, UrbanNormalCannibalLike, UrbanHungryCannibalLike](name,"fork") with UrbanHungryCannibalLike {
def birthChild(childName : String) : TChild = UrbanHungryCannibal(completeNameOfChild(childName))
}
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// Adopting cannibal traits and case classes
/////////////////////////////////////////////////////////////////////////////////////
trait AdoptingCannibalLike extends AnyCannibalLike {
//Adopting cannibals can adopt any hungry cannibal.
//If the adopter is hungry then the returned adopted should keep its type as is.
//If the adopter is not hungry then the returned adopted is the same type but not hungry.
//how to do this?
//def adoptHungryCannibal(hungryCannibal : AnyHungryCannibalLike[_]) : hungryCannibal.type = hungryCannibal
//val ajhc = adoptHungryCannibal(JungleHungryCannibal("jhc"))
}
trait AdoptingNormalCannibalLike extends AnyNormalCannibalLike[AdoptingCannibalLike] with AdoptingCannibalLike
trait AdoptingHungryCannibalLike extends AnyHungryCannibalLike[AdoptingCannibalLike] with AdoptingCannibalLike
case class AdoptingNormalCannibal(name : String) extends AnyNormalCannibalA[AdoptingCannibalLike, AdoptingNormalCannibalLike, AdoptingHungryCannibalLike](name) with AdoptingNormalCannibalLike {
def birthChild(childName : String) : TChild = AdoptingNormalCannibal(completeNameOfChild(childName))
}
case class AdoptingHungryCannibal(name : String) extends AnyHungryCannibalA[AdoptingCannibalLike, AdoptingNormalCannibalLike, AdoptingHungryCannibalLike](name,"my kids") with AdoptingHungryCannibalLike {
def birthChild(childName : String) : TChild = AdoptingHungryCannibal(completeNameOfChild(childName))
}
/////////////////////////////////////////////////////////////////////////////////////
Test code
val jnc = JungleNormalCannibal("jnc")
val jhc = JungleHungryCannibal("jhc")
val unc = UrbanNormalCannibal("unc")
val uhc = UrbanHungryCannibal("uhc")
val cjnc = jnc.birthChild("cjnc")
val cjhc = jhc.birthChild("cjhc")
//Must execute
jnc.playWith(jnc)
jnc.playWith(unc)
jhc.eat(jnc)
uhc.eat(unc)
jnc.huntWith(jhc)
jhc.huntWith(jnc)
cjnc.huntWith(jhc)
cjhc.huntAndEat(jnc)
//Must produce compilation errors
jnc.huntWith(unc)
jnc.eat(jnc)
cjnc.huntWith(unc)
cjnc.eat(jnc)
Problems
- Unable to implement changeTo[,,] which creates a new cannibal with the same name but different type (I'm guessing both implicits and macros need to be utilized in-order to achieve this, but I have no idea how.
- Unable to implement adoptHungryCannibal. Similar problem to #1?
- The code seems too verbose. To create a new tribe we repeat a lot of code. Any suggestions how to simplify it (Macros, dependency injections)?
Notes
Since I am fairly new to Scala, I may not have written conventional code. Please point out mistakes if you see any.
Aucun commentaire:
Enregistrer un commentaire