SVX日記
2023-11-10(Fri) CoffeeScript2にてMixinを試す
ここのところ、ものスゴい学習欲と、それを越えるほどの課題が湧き上がってきて頭を休める間もない。WebAssemblyからSIMD命令(MMX/SSE/AVX)、浮動小数点演算、CPUキャッシュに飛び火し、なぜか線形代数の学び直しから、3DCG、AIに至るまで。
しかし最終的にアニメーションさせたいなら、以前に作ったシューティングゲームの既存のフレームを使ったほうがいい、ということになり、久々に引っ張り出してくると……動かねぇ。
どこが引っかかっているのかと思ったらMixinだ。なぜか以前に書いたMixinの仕組みが動かない。まぁJavaScriptとしても、CoffeeScriptとしても、Mixinが正式な言語仕様として組み込まれているわけではないからな、と思いつつ、調べていくと意外と根が深い。
そもそもJavaScriptにはクラスが存在しない。いや、最近まで存在しなかった。CoffeeScriptで書いていたので気づかなかったが、クラスが正式な言語仕様として組み込まれているわけではないところに、prototypeとかいう仕組みを通じてCoffeeScriptによりコネあげられていたのだ。ところが、最近になってJavaScriptにクラスの仕組みが組み込まれたので、CoffeeScript2からはコネあげをやめ、その仕組みをそのまま利用するようになった。
それだけならよかったのだが、クラスでメソッドを定義するとprototypeに登録されないようなのだ。先のMixinの仕組みはprototypeを通じてコネあげられていたので、それができなくなってしまった、と。
// コネあげ版クラス(JavaScript)
const Animal = (function() {
    function Animal(name) {
        this.name = name;
    }
    Animal.prototype.walk = function() {
        console.log(this.name, 'walking.');
    };
    return Animal;
})();// ネィティブクラス(JavaScript)
const Animal = class Animal {
    constructor(name) {
        this.name = name;
    }
    walk() {
        console.log(this.name, 'walking.');
    }
};// どちらも同様に動くのだが
console.log(Animal.prototype);
const pochi = new Animal('Pochi');
pochi.walk();// コネあげ版クラスだとprototypeにwalkがあるのに
$ node diff_func.js
Animal { walk: [Function] }
Pochi walking.// ネィティブクラスだとprototypeがカラ
$ node diff_class.js
Animal {}
Pochi walking.const Flying = (base) => class extends base {
    fly() {
        console.log(this.name, 'flying.');
    }
}
class Bird extends Flying(Animal) {};以前は「mixOf base, mixin」という以下のような書き方だったが「Flying(Animal)」って書き方も悪くはないなぁ。従来のmixOf関数の定義は必要なくなる。上記の定義自体がMixinを行う関数になっている。
class Bird extends mixOf Animal, Flying'use strict'
 
class Animal
    constructor: (name) ->
        @name = name
    walk: ->
        console.log(@name, 'walking.')
 
class Human extends Animal
    talk: ->
        console.log(@name, 'talking.')
 
Flying = (base) -> class extends base
    fly: ->
        console.log(@name, 'flying.')
 
Ejecting = (base) -> class extends base
    eject: ->
        console.log(@name, 'ejecting.')
 
class Bird extends Flying(Animal)
 
class Pilot extends Ejecting(Flying(Human))
 
pochi = new Animal('Pochi')
pochi.walk()
 
taro = new Human('Taro')
taro.walk()
taro.talk()
 
pichan = new Bird('Pi-chan')
pichan.walk()
pichan.fly()
 
tom = new Pilot('Tom')
tom.walk()
tom.talk()
tom.fly()
tom.eject()$ node mixin.js
Pochi walking.
Taro walking.
Taro talking.
Pi-chan walking.
Pi-chan flying.
Tom walking.
Tom talking.
Tom flying.
Tom ejecting.