DWEMTHY_S ARRAY^H^H^H^H^HLIST IN SCALLY
Last week the satanic Dwemthy’s Array was released into the quiet, pious town of Java programming. { A, B } Both Java permutations used reflection to emulate some dynamic properties of the original, a recipe for sad exception handling if there ever was one. But it was helpful to be reminded of a few techniques to fight the clutter beast in Java code, even if defeating it is impossible.
The crux of the Ruby original is probably that method_missing
can make an array act as one of its elements, which is the sort of thing Ruby is good at. An interesting topic to explore! But the game itself is cool, and after seeing the comment
# lettuce will build your strength and extra ruffage
# will fly in the face of your opponent!!
it became pretty impossible not to translate it into the house favorite language, Scala.
There is no sense in imitating the dynamic typing pirouettes of the original using Scala, but this Dwemthy permutation will try to make up for the lost dynamic awesome with other kinds of awesome that Scala is good at, like functional programming and mostly painless typing.
And, Scala like Ruby allows you to define methods that look like operators, declare multiple base types in one file, and it has an interpreter. That way the player can load a single source and play within the interpreter, which is the real ultimate power of Dwemthy. The Java implementations, with their class source file ensembles and keyboard input loops, have shown up at the beach wearing business casual yet again.
// clock-seeded pseudorandom sequences are for sharing!
object Random extends Random
trait Creature {
val life: Int
val strength: Int
val charisma: Int
val name: String
// abstract type to escape from square braces[][][][]
type Copy <: Creature
def copy(life: Int): Copy
def rand(until: Int) = Random.nextInt(until)
def hit(damage: Int) = copy(
(rand(charisma) match {
case p_up if p_up % 9 == 7 =>
printf("[%s magick powers up %d!]\n", name, p_up)
p_up / 4
case _ => 0
}) + life - damage match {
case death if death <= 0 =>
printf("[%s has died.]\n", name)
death
case l => l
}
)
}
A Creature is something that can be hit with damage and that has certain attributes. Note that these are immutable. The hit
method returns a new copy of the Creature with a new life value, thus it requires an abstract method to produce this copy and an abstract type to say what that copy will be. Otherwise, the value returned by hit would simply be a Creature and we could no longer use the wonderful weapons that only the rabbit possesses. You can do the same thing with type parameters, but the abstract type is self-documenting and doesn’t clutter up the class/trait introduction with distracting symbols.
Pattern matching! If you want to know why it’s used here instead of a plain old if
s, the answer is that match
gives you a nicely scoped reference to its input object. That makes it easier to write functions that evaluate to a single expression instead of a series of expressions and a bunch of values for things that you need to use just twice. Although, it seems like there may be an even more awesome way to do this kind of thing; if you know one, please don’t keep it to yourself!
trait Enemy extends Creature {
// enemies beget enemies
type Copy = Enemy
// and hold one (1) weapon
val weapon: Int
}
Yep. In the Ruby version, rabbit has a weapon value that isn’t used in normal gameplay, so here in pedantic typing land we will just put that right in Enemy where it belongs. Also now the Copy
type is set once and for all enemies with this trait.
case class Rabbit(rem_life: Int, bombs: Int) extends Creature {
// and rabbits make... MORE RABBITS
type Copy = Rabbit
val life = rem_life
val strength = 2
val charisma = 44
val name = "Rabbit"
def fight(enemy: Enemy, weapon: Int) =
if (life <= 0) {
printf("[%s is too dead to fight!]\n", name)
(this, enemy)
} else {
// Attack the opponnent
val your_hit = rand(weapon + strength)
printf("[You hit with %d points of damage!]\n", your_hit)
val damaged_enemy = enemy hit your_hit
//Retaliation
println(damaged_enemy)
( if (damaged_enemy.life > 0) {
val enemy_hit = rand(damaged_enemy.weapon + damaged_enemy.strength)
printf("[Your enemy hit with %d points of damage!]\n", enemy_hit)
this hit enemy_hit
} else this
, damaged_enemy)
}
def copy(life: Int) = new Rabbit(life, bombs)
// little boomerang
def ^ (enemy: Enemy) = fight(enemy, 13)
// the hero's sword is unlimited!!
def / (enemy: Enemy) =
fight(enemy, rand(4 + (enemy.life % 10) * (enemy.life % 10)))
// lettuce will build your strength and extra ruffage
// will fly in the face of your opponent!!
def % (enemy: Enemy) = {
val lettuce = rand(charisma)
printf("[Healthy lettuce gives you %d life points!!]\n", lettuce)
Rabbit(life + lettuce, bombs).fight(enemy, 0)
}
// bombs, but you only have three!!
def * (enemy: Enemy) =
if (bombs <= 0) {
println("[UHN!! You're out of bombs!!]")
(this, enemy)
} else
Rabbit(life, bombs-1).fight(enemy, 86)
}
object Rabbit extends Rabbit(
10, // life
3) // bombs
(Ruffage is condensed roughage.) This is pretty self explanatory maybe. All case classes get a halfway decent automatic toString
that prints their constructor values. The deal with fight
and all the weapon-specifc methods is, they return a tuple made up of your guy and the enemy. We need this because both usually change in one turn, thanks to retaliation.
// the P is for ''''PRIME''''
case class EnemyP(life:Int, strength:Int, charisma: Int, weapon: Int,
name: String) extends Enemy {
// https://lampsvn.epfl.ch/trac/scala/ticket/321 grrr!!!
def copy(life: Int): Enemy = EnemyP(life, strength, charisma, weapon, name)
}
// Critters are easy to define
trait Critter extends Enemy {
def copy(life: Int): Enemy = EnemyP(life, strength, charisma, weapon, name)
}
Done! Now we can conjure ScubaArgentine.
object ScubaArgentine extends Critter {
val life = 46
val strength = 35
val charisma = 91
val weapon = 2
val name = "Scuba Argentine"
}
And have an interpretive fight.
scala> var play = Rabbit / ScubaArgentine
[You hit with 5 points of damage!]
EnemyP(41,35,91,2,Scuba Argentine)
[Your enemy hit with 27 points of damage!]
[Rabbit has died.]
play: (Rabbit, Enemy) = (Rabbit(-17,3),EnemyP(41,35,91,2,Scuba Argentine))
Aw. You can see that toString
is not ideal. We’ll get back to that, and the names. For now, replay the match until rabbit survives a round. play
is the only variable in this game, so that it can be updated and reused with each turn.
scala> play = Rabbit / ScubaArgentine
[You hit with 2 points of damage!]
EnemyP(44,35,91,2,Scuba Argentine)
[Your enemy hit with 0 points of damage!]
play: (Rabbit, Enemy) = (Rabbit(10,3),EnemyP(44,35,91,2,Scuba Argentine))
Now we’re talking. And to play the next turn:
scala> play = play._1 % play._2
Or
scala> play = play match { case (r, e) => r % e }
And so on! (The interpreter’s input history is helpful.) Now for the evil DwemthysList.
// is-NOT-a-list-but-*has*-one and that will have to do
class DwemthysList(head: Enemy, tail: List[Enemy]) extends EnemyP(
head.life, head.strength, head.charisma, head.weapon, head.name) {
override def copy(life: Int) =
if (life <= 0) {
if (tail.isEmpty) {
println("[Whoa. You decimated Dwemthy's Array!]")
head.copy(life)
} else {
printf("[Get ready. %s has emerged.]\n", tail.head.name)
new DwemthysList(tail.head, tail.tail)
}
} else new DwemthysList(head.copy(life), tail)
}
// but can-construct-as-a-list!
implicit def list2dwemthy(list: List[Enemy]) = new DwemthysList(list.head, list.tail)
The awesome dynamicy implicit was J. I.’s idea.
That list2dwemthy
will turn a regular list into a DwemthysList
whenever one is required. And finally, code is data or whatever.
object IndustrialRaverMonkey extends Critter {
val life = 46
val strength = 35
val charisma = 91
val weapon = 2
val name = "IndustrialRaverMonkey"
}
object DwarvenAngel extends Critter {
val life = 540
val strength = 6
val charisma = 144
val weapon = 50
val name = "DwarvenAngel"
}
object AssistantViceTentacleAndOmbudsman extends Critter {
val life = 320
val strength = 6
val charisma = 144
val weapon = 50
val name = "AssistantViceTentacleAndOmbudsman"
}
object TeethDeer extends Critter {
val life = 655
val strength = 192
val charisma = 19
val weapon = 109
val name = "TeethDeer"
}
object IntrepidDecomposedCyclist extends Critter {
val life = 901
val strength = 560
val charisma = 422
val weapon = 105
val name = "IntrepidDecomposedCyclist"
}
object Dragon extends Critter {
val life = 1340 // tough scales
val strength = 451 // bristling veins
val charisma = 1020 // toothy smile
val weapon = 939 // fire breath
val name = "Dragon"
}
val dwlist = List(IndustrialRaverMonkey,
DwarvenAngel,
AssistantViceTentacleAndOmbudsman,
TeethDeer,
IntrepidDecomposedCyclist,
Dragon)
Now you can attack the list.
scala> var play = Rabbit / dwlist
Maybe you will win—anything is possible! Hint, try not to forget the game is played in a programming environment. Dwemthy didn’t say anything about writing recursive functions, so, why not. Simple algorithms have been known to get to the dragon, but probably can’t beat him without overflowing int. Yeah it’s that serious.
dwemthy.scala
And about that toString
—lame. Ruby has a much nicer display of the attributes than the case class is giving, at least the way it’s used here. Lamer still is the fact that we’re naming the creatures with Strings instead of using type information, but, that’s harder than it looks. Can you even get that information at construction time? In a trait? Maybe a lazy val
would work, but then the case class won’t output it? There has got to a way to improve the name and status printing without a bunch of uglycode. And since the source above is hosted at the new technically.us
git node, you can totally clone and branch.
But whatever you do, do not put a variable in there or Dwemthy will personally show you a whole new world of hurt.
Codercomments
I’m not sure that I agree with the decision to make creatures immutable. I mean, I like immutable data as much as the next guy, but some things are just naturally statefull (that’s why Scala has var). It just seems like it would be more natural if you didn’t have to keep worrying about managing the creature by hand-ish.
That is the kind of thinking that will talk you out of ever using immutable state! In theory it’s appropriate for everything; the more state to be maintained, the greater the clarity and reliability advantage. Not that I buy that necessarily, but it worked nicely in this trial. And otherwise it would have been boringly too much like the Ruby version.
Actually, immutable data is even better for a game than for everything else.
Want a nice display of the game’s history at the end? No problem, just keep all of the data objects and iterate over them to tell the story (mostly).
Want a magic weapon that rewinds time? With immutable state, you just hang onto the old objects for a bit. With mutable state, you have to keep enough data to reverse whatever you did.
Yeah, having a state history is sweet. If you play this game in the Scala interpreter and don’t assign a play variable, you get automatic incremental references to character snapshots. But then you also have to increment your input for the next turn.
Does anybody know of good immutable examples around the web, preferably in a language that most people can read? I couldn’t find squat. Mostly I wanted to know the typical names for functions and values that would have to be common in this kind of programming, but I’m sure there’s also a lot of techniques I didn’t arrive at on my own.
The best examples of advanced immutable programming are going to be in languages like Haskell, ML, and Scheme. That kind of design isn’t as well used in any of the more popular languages. But that’s okay, learning those languages builds even more strength and ruffage than eating lettuce.
A thought: instead of object DwemthysList {def apply…}, how ‘bout this?
Seems to capture some of the spirit of the original
method_missing
.As for getting names of classes and properties and such, Scala doesn’t currently offer a better answer than Java reflection. You can “pimp” the reflection library a bit to make it a little cleaner to use, but it’s still reflection in the end.
Perhaps some day Scala will offer template meta-programming or at least something along the lines of Haskell’s SYB.
Hi James, I suppose it would at least be worth learning to read those languages. Maybe then I will understand MONADS. (I did enjoy your series on them, but my brief, partial cognizance of
flatMap
has since evaporated.)I love the implicit List conversion idea, will definitely make that change.
The thing with using reflection for the name is it can’t produce a value for the case class.
I guess Scala makes a fake
this
for the constructor and the class it gives back is nameless. Fine. I mean, I wouldn’t expect that work anyway.:\
What you’re doing there is equivalent to val name = getClass.getName class NamedThing extends A(name)
It is interesting that the interpreter’s top level object has a class with no name. I had never even thought to look.
Anyway, to achieve the desired effect would require overriding toString with a bunch of reflective stuff. So it’s right back to the Java solution :(
Yes, I suppose that’s how it’s interpreted. Dwemthy is going to have to be satisfied with this for the time being. If a reflective class and property pretty-printer (like Ruby’s) ever shows up in stdlib, that would be cool. Or some way to do
classOf
on abstract types/type parameters. (I don’t see why those can’t be statically inserted by the compiler.)That is a fun read, n8! Thanks for sharing.
Regarding state, one place where it helps is when you want to have references between the objects and you always want to point to the current version. For example, imagine if you had an arena that had pointers to all the fighters. If you insist on no state, then whenever any fighter changes, you have to update the whole arena. This can require a lot of boilerplate code, plus if you leave out an update, you can end up using the wrong version of something without even getting any error from the runtime system at all.
So, long story short, my instinct matches some others and I’d suggest making the creatures statefull but have as many as possible of their fields being vals or at least referencing data that is itself immutable.
For run-time type information, a pattern that is often used is to have a TypeDescriptor hierarchy and then to pass these around as implicit parameters. In this case, though, nothing should prevent you from writing some toString() methods, but I admit I haven’t thought about what the implementation of those methods would look like.
Hey, Lex, thanks for Programming in Scala!
I see what you’re saying about the arena. I guess the question with immutable state is always going to be, where does it stop? Here I think it works out the way it is, but it would be interesting to compare it to a less aggressively immutable version. (The LoC would go down a little I bet.)
Add a comment