2021-01-28

[Flash] Functional JavaScript

[Flash] JavaScriptで学ぶ関数型プログラミング

Environment:

  • Adobe AIR SDK 29
  • FlashDevelop 5.5.3
  • Windows 10

[話者] 書籍『JavaScriptで学ぶ関数型プログラミング』を読んだんだ。 まず、こんな問題意識があった。

AS3でオブジェクト指向の継承使ってゲームのシーン遷移を書いた。 けど、追跡しにくい。もっと簡単に書けるだろ? 関数型JavaScript使えば簡単になる? 読み直す。

ところでAS3は JavaScript (ES4) である。 ES4 (EcmaScript 4) というのは、JavaScript (ES3) + Javaふうのオブジェクト指向 + Javaふうの静的型付け という言語で、TypeScriptのご先祖様的な言語だ。

AS3はJS(ES6=ES2015)のfat arrow関数が使えない。 だから

var func = (a,b) => {a+b*2};

じゃなくて

var func:Function = function(a:int,b:int):int{return a+b*2;};

と書く必要がある。

しかし、せめて型指定なしで

var func = function(a,b){return a+b*2;}

と書きたい。

http://cuaoar.jp/2007/07/-as3-strict.html によるとFlashDevelopの [Project>Properties>Compiler Option>Enable Strict Mode] False にするといい。

型宣言をしないと Object 型の変数になります。 個別に型指定の変数を宣言することも可能です。

http://cuaoar.jp/2005/11/post-1.html

勝手に空白追加するのは止めろFlashDevelop。

[Tools>ProgramSetting>ASCompletions>Always Add Space After]'if for while do catch with' -> ''
[Tools>ProgramSetting>ASCompletions>Characters Requiring Whitespace]',;*+-=/%<>|&!^' -> ''

JSなら

var input = [80, 90, 100];
var sum = (total, current) => total + current;
var total = arr => arr.reduce(sum);
var size = arr => arr.length;
var divide = (a, b) => a / b;
var average = arr => divide(total(arr), size(arr));
average(input); // -> 90

をAS3なら

var input = [15, 45, 60];
var total = function(arr){var t=0; for(i=0;i<arr.length;i++){t+=arr[i]};return t};
trace(total([1,4,9])); // -> 14
var size = function(arr){return arr.length};
var divide = function(a, b){return a / b};
var average = function(arr){return divide(total(arr), size(arr))};
trace(average(input)); // -> 40

と書ける。Arrar.reduce()がないのでtotal()は書き直した。

reduce()を自前で用意するなら以下。

var sum = function(total, current){return total+current};
var reduce = function(arr, fn, acc){
    var idx = -1;
    var len = arr.length;
    if ( ! acc && len>0) acc = arr[++idx];
    while (++idx<len){
        acc = fn(acc,arr[idx],idx,arr);
    }
    return acc;
};
total = function(arr){return reduce(arr,sum)};
trace('average:'+average(input)); // -> 40

関数合成みたいなもの。functionをreturnするのがポイントか。

var applyOperation = function(a, b, func){return func(a,b)};
var multiplier = function(a,b){return a*b};
trace('applyOperation:'+applyOperation(2,5,multiplier)); // -> 10

var add = function(a){return function(b){return a+b}};
trace('add:'+add(12)(9)); // -> 21

var makeFunc = function(a){return function add(b){return a+b}};
var addTo100 = makeFunc(100);
var addTo200 = makeFunc(200);
trace('addTo100:'+addTo100(75)); // -> 175
trace('addTo200:'+addTo200(15)); // -> 215

オブジェクト指向のclassにあるprivate変数を、JavaScriptで実現する方法

//emulate private variables (IIFE immediately invoked function expression)
var MyModule = (function MyModule(expt){
    var a = 10;
    expt.func1 = function(b){return 'MyModule:'+(a+b);}
    return expt;
}(MyModule || {}));
trace('MyModule:'+MyModule.func1(300)); // -> 310
MyModule.a = 20;
trace('MyModule:'+MyModule.func1(500)); // -> not 520, but 510

var MyClass = function(arg){
    this.prop = 12 + arg;
    this.getProp = function(a){a=a||0; return 'MyClass:'+(this.prop+a);}
};
var obj1 = new MyClass(25);
var obj2 = new MyClass(88);
trace('obj1:'+obj1.getProp()); // -> 37
trace('obj2:'+obj2.getProp()); // -> 100
obj1.prop = 1024;
trace('obj1:'+obj1.getProp()); // -> 1024
trace('obj1:'+obj1.getProp(256)); // -> 1280

var obj = {
    prop : 'abc',
    getProp : function(){return this.prop;}
};
trace('obj:'+obj.getProp()); // -> 'abc'
obj.prop = 'def';
trace('obj:'+obj.getProp()); // -> 'def'

as3で型付けなしでコンパイル可能なのはいいけどwarningがうっとうしい。

http://www.docsultant.com/site2/articles/flex_cmd.html

foo@sfb2 MINGW64 /r/wk

$ vi Main.as
    package {
    public class Main {
      public function Main(){
        var func = function(n){return n+1};
        trace("Hello World "+func(20));
      }
    }
    }

$ mxmlc -warn-no-type-decl=false Main.as
    読み込みの設定 : C:\home\bin\flashdevelop\Apps\air29\frameworks\flex-config.xml
    R:\wk\Main.as:4
    警告 : 関数「」の戻り値に、型宣言がありません。
        var func = function(n){return n+1};
                   ^
    0.546 秒で R:\wk\Main.swf に 492 バイトが書き込まれました


$ mxmlc -warn-no-type-decl=true -tools-locale=en Main.as
    Loading configuration: C:\home\bin\flashdevelop\Apps\air29\frameworks\flex-config.xml
    R:\wk\Main.as:4
    Warning: return value for function '' has no type declaration.
        var func = function(n){return n+1};
                   ^
    R:\wk\Main.as:4
    Warning: variable 'func' has no type declaration.
        var func = function(n){return n+1};
            ^
    492 bytes written to R:\wk\Main.swf in 0.597 seconds

$ mxmlc -warn-no-type-decl=false -tools-locale=en Main.as
    Loading configuration: C:\home\bin\flashdevelop\Apps\air29\frameworks\flex-config.xml
    R:\wk\Main.as:4
    Warning: return value for function '' has no type declaration.
        var func = function(n){return n+1};
                   ^
    492 bytes written to R:\wk\Main.swf in 0.540 seconds

これ以上warning減らす方法は無いようだ。

functionの補完が勝手に:voidとか改行になるのはFlashDevelop設定でなんとかならないか。

-> 設定では無理そう。AutoHotKeyで NoConv+n でなんとかした。

本で Lodash 使っているところを underscore.as 使おうとしたが、underscore.as 不要だった。

package {
import flash.display.Sprite;
import flash.events.Event;
import com.alanmacdougall.underscore.*;
[SWF(width="640", height="480", frameRate="30", backgroundColor="#eeeeee")]
public class Main extends Sprite {
    public function Main() {
        if (stage) init();
        else addEventListener(Event.ADDED_TO_STAGE, init);
    }
    private function init(e:Event = null):void {
        removeEventListener(Event.ADDED_TO_STAGE, init);
        // entry point
        var persons = [
            {id:0,firstname:'Haskell',lastname:'Curry',country:'US',year:1900},
            {id:1,firstname:'Barkley',lastname:'Rosservon',country:'Greece',year:1907},
            {id:2,firstname:'John',lastname:'Neumann',country:'Hungary',year:1903},
            {id:3,firstname:'Alorizo',lastname:'Church',country:'US',year:1903},
            {id:4,firstname:'David',lastname:'Hilbert',country:'Germany',year:1862},
            {id:5,firstname:'Alan',lastname:'Turing',country:'England',year:1912},
            {id:6,firstname:'Stephen',lastname:'Kleene',country:'US',year:1909}
        ];

        Array.prototype.orderBy = function(func){
            if ( ! func)
                func = function(a,b){return true;}
            this.sort(func);
            return this;
        }

        //というかライブラリ(underscore.as)不要
        var r;
        r = persons
            .map(function(s){return s.firstname + " " + s.lastname})
            .filter(function(s){return s.indexOf('A')>=0})
            .orderBy()
            .map(function(s){return s.replace(' ','-')})
        trace('r:'+r);             // -> r:Alan-Turing,Alorizo-Church
        trace(typeof(r));          // -> object [ "Alan-Turing", "Alorizo-Church" ]
    }
}}

異なるデータ型に新たな動作を与えるという点では、オブジェクト指向プログラミングでは継承が行い、関数型プログラミングでは関数合成が行うことになります

この本、ここまではよかった。 途中から外部ライブラリ(LodashやRamda)の使用法説明みたいになる。 外部ライブラリ使用して、これがモナドですとか言われても汎用性無い。 素のjsでどこまでもやってほしかった。

amazon.comの書評が的確だと思った。

著者は、なぜオブジェクト指向のコードがダメなのかという話から始まり、 説明もなく終始LodashのチェーンやES6クラスを使って話を進めていきます。

レンズは導出も説明もされていませんし、Lodash と Ramda 関数は全く読めないコードになり、 命令法と関数法と OO コードは入れ替わり立ち替わりで使われています。

もっと良い本を探している人には、BraithwaiteのJavaScript Allongeをお勧めします。

https://www.amazon.com/Functional-Programming-JavaScript-Javascript-Techniques/dp/B07KFPZLQ6

JavaScriptで関数型技術の基礎をしっかりと教えてくれるらしい。 https://github.com/raganwald/javascript-allonge

ES6版もあるが、あえてES5版を読む。

...その本で進められてたJavaScript Ninja本読んでみる。

0 件のコメント:

コメントを投稿

人気記事