[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 型の変数になります。 個別に型指定の変数を宣言することも可能です。
勝手に空白追加するのは止めろ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 件のコメント:
コメントを投稿