Dynamic class generation in CoffeeScript

What is the best way to dynamically create classes in CoffeeScript so you can create objects from them later?

I found ways to do this, but I'm not sure if there is an even better (or simpler) way to achieve it. Please let me know your thoughts on my code.

Let's start with simple non-dynamic classes:

class Animal constructor: (@name) -> speak: -> alert "#{@name} says #{@sound}" class Cat extends Animal constructor: (@name) -> @sound = "meow!" garfield = new Cat "garfield" garfield.speak() 

As expected, Garfield says meow!

But now we want to dynamically generate classes for more animals, which are defined as follows:

 animalDefinitions = [ kind: 'Mouse' sound: 'eek!' , kind: 'Lion' sound: 'roar!' ] 

The first naive attempt fails:

 for animal in animalDefinitions animal.class = class extends Animal constructor: (@name) -> @sound = animal.sound mutant = new animalDefinitions[0].class "mutant" mutant.speak() 

The animal we just created, mutant , must be a mouse. However, he says a roar! This is because animal.sound is evaluated only when instantiating the class. Fortunately, from JavaScript we know a proven way to solve this problem: closing:

 for animal in animalDefinitions makeClass = (sound) -> class extends Animal constructor: (@name) -> @sound = sound animal.class = makeClass(animal.sound) mickey = new animalDefinitions[0].class "mickey" mickey.speak() simba = new animalDefinitions[1].class "simba" simba.speak() 

Now it works as desired, mickey mouse says eek! and the simba lion speaks a roar! But this is already somewhat complicated. I am wondering if there is an easier way to achieve this result, perhaps by directly contacting the prototype. Or am I completely mistaken?

+4
source share
2 answers

Since sound is the default value for an instance of Animal, you can set it as a property in the class definition:

 class Cat extends Animal sound: 'meow!' garfield = new Cat "garfield" garfield.speak() # "garfield says meow!" 

then

 for animal in animalDefinitions animal.class = class extends Animal sound: animal.sound mutant = new animalDefinitions[0].class "mutant" mutant.speak() # "mutant says eek!" 

If you want sound be redefined, you can do

 class Animal constructor: (@name, sound) -> @sound = sound if sound? speak: -> console.log "#{@name} says #{@sound}" 
+3
source

For your immediate problem (which is that the closure in the loop does not fix the current value, but the last one), there is a do construct:

 for animal in animalDefinitions do (animal) -> animal.class = class extends Animal constructor: (@name) -> @sound = animal.sound 

I somehow expected CoffeeScript to take care of this automatically, as this is a common JavaScript error, but at least with do there is a compressed way to write it.

+2
source

All Articles