サーバーをいじれないもどかしさ
とあるネットショップのホームページを作成しているのですが、そこではサーバー側のデータベースにアクセスできません。
ですので商品の配置を自分好みにしたいときは、まずサーバーからHTML文書を取得し、そこから JavaScript で書き換えるという手間を踏まなければなりません。
これは明らかに二度手間なので何とかしたいのですが、いい代替案が思いつきません。
それに PageSpeed Insights でサイトの点数を見てみると50点代で、点数を上げる方法に「キャッシュの有効期限を設定する」があり、これも今の状況では何ともできません。
あとメールソフトを開かずにメールを送れるようにしたいのですが、これも JavaScript だけでは無理で、どうしようかと困っています。
- サーバー側で文書作成 → クライアント側で DOM を再構築。これは二度手間
- キャッシュの有効期限を設定できない
- メールソフトなしでメールを送れない
契約期間満了が 8 月なので、それまでに仮想サーバーでネットショップを作成して、契約が切れたらそちらを公開しようかな。
自社でサーバーを持つのは保守性に問題があるのでどこかのサーバーを借りて。
共通部分を一つにするのは楽しい
前回の記事では、if...else 文の中を共通化することができませんでした。しかし、関数型プログラミングの考えを取り入れることで、共通化することができました。
function move(com, i) { _.each(rangeOne(i, com === 0 ? 100 : 0), function(i) { /* 共通部分 */ }); } // 1ずつ増減する要素の配列を返す関数 function rangeOne(start, end) { return _.range(start, end, start < end ? 1 : -1); }
前回のコードです。
function move(com, i) { function common() { // 共通部分 } if (com === 0) { while (i < 100) { common(); i += 1; } } else { while (i > 0) { common(); i -= 1; } } }
_.each メソッドや _.range メソッドは、Underscore.js*1 で定義されているメソッドです。Underscore.js は、JavaScript で関数型プログラミングをするために作られた JavaScript ライブラリです。各メソッドの説明をしてみます。
_.each メソッド
第一引数に配列を、第二引数に関数を渡します。
このメソッドの仕事は、配列の各要素を関数に渡すことです。
_.each([1, 3, 5], function(n) { console.log(n * n) }) // => コンソール画面に「1 9 25」と表示される
_.range メソッド
全ての引数は数値です。
このメソッドの機能は、新たな配列を作成することです。
_.range(10, 15, 1) // => [10, 11, 12, 13, 14] _.range(10, 0, -2) // => [10, 8, 6, 4, 2] _.range(10, 5, 1) // => []
_.range メソッドの第三引数を省略すると 1 が入ります。第二引数が第一引数よりも小さいか、等しいとき、空の配列が作成されます(例の 3 番目)。
この 2 つのメソッドを使用することにより、前回成し遂げられなかった共通化ができました。とても嬉しかったです。副産物として rangeOne 関数という汎用性の高い関数もできました。これからも、共通部分を一つにすることを意識したいと思います。
今回のコードにはバグが潜んでいました。
例えば i 変数が 200 で com 変数が 0 のとき、前回のコードではループが一度も実行されないにも関わらず、今回のコードでは 100 回のループが実行されます。com 変数ではなく rangeOne 関数が増減の方向を決めているからです。以下のように訂正しました。
function move(com, i) { _.each(rangeMake(i, com===0?[100,1]:[0,-1]), function(i) { /* 共通部分 */ }); } function rangeMake(start, arr) { return funcApply(_.range, [start].concat(arr)); } function funcApply(func, args) { return func.apply(null, args); }
funcApply 関数は汎用性が高いですが、rangeMake 関数は汎用性が低いかもしれません。コードが横に長くなり過ぎないように rangeMake 関数を作ったのですが、はたしてこれは正しいのか…。そのような動機で関数を作るなら、頑張って整形したほうがいいのではないか。でも、うまいこと整形する方法がわからなかったので、rangeMake 関数という汎用性の低そうな関数を作成してしまったわけです。
とりあえず、今回はこれを完成形としておきます。ではまた。
ちょっと気になることがあったのでまたまた追記。
funcApply 関数の必要性があまりないように感じられました。
function move(com, i) { _.each(rangeMake(i, com===0?[100,1]:[0,-1]), function(i) { /* 共通部分 */ }); } function rangeMake(start, arr) { return _.range.apply(null, [start].concat(arr)); }
こちらのほうがいいかもしれません。
*1:公式サイト http://underscorejs.org/
関数を入れ子構造にできることの利点
関数の入れ子構造が便利だということを認識したので書いておきます。
function move(com, i) { if (com === 0) { while (i < 100) { // 共通部分 i += 1; } } else { while (i > 0) { // 共通部分 i -= 1; } } }
今までは、このようなコードの共通部分を、move 関数と同じ階層に新たな関数を定義してまとめていました。
function move(com, i) { if (com === 0) { while (i < 100) { common(); i += 1; } } else { while (i > 0) { common(); i -= 1; } } } function common() { // 共通部分 }
しかしこの方法では、move 関数のローカル変数が増え、その変数を共通部分で使用するときに面倒くさいことになります。例えば、move 関数内で新たに6つの変数を宣言し、それらの変数を共通部分で使用するとき、6つの変数を common 関数に渡さなければなりません。
function move(com, i) { var a, b, c, d, e, f; // ローカル変数の値を書き換える処理 if (com === 0) { while (i < 100) { common(a, b, c, d, e, f); i += 1; } } else { while (i > 0) { common(a, b, c, d, e, f); i -= 1; } } } function common(a, b, c, d, e, f) { // 共通部分 }
このときに書き換える部分は、common 関数の定義部と呼び出し部分の合計3ヶ所です。比較的面倒くさい作業になります。
それと、move 関数の6つのローカル変数を common 関数で使用することから、move 関数と common 関数は密結合であることがわかります。ということは、common 関数が move 関数以外から呼び出される可能性は限りなく0に近いです。そのような関数を、グローバル関数として定義するのは気が引けます。
common 関数を move 関数内で定義することで、この問題は解決します。
function move(com, i) { function common() { // 共通部分 } if (com === 0) { while (i < 100) { common(); i += 1; } } else { while (i > 0) { common(); i -= 1; } } }
こうすることにより、common 関数の引数を増やす必要はなくなりますし、common 関数をグローバル関数として定義する必要もありません。ここから、関数の入れ子構造が便利であることがわかりました。
しかしまだ不満が残ります。それは、if...else 文の中の構造が同じであることです。
while ([...]) { common(); [...]; // i 変数の増減 }
もしこの部分をまとめる方法があるのなら、common 関数を取り除くことができます。その方法を考えたのですが、eval関数を使用する方法しか思いつきませんでした。もっとスマートに解決する方法があるかもしれません。もしかしたら、関数型プログラミングの手法を取り入れることで、この問題をスマートに解決できるかもしれません。関数型プログラミングが十分に使いこなせるようになったときに、また考えてみようと思います。
指向の違いとコードの違い
3日前に『JavaScript で学ぶ関数型プログラミング』を購入し、一章と二章を読み終えたのですが、JavaScript の奥深さを再認識し、とても面白く感じています。
// 命令型プログラミング function fizzbuzz(max) { var result = "", i; for (i = 1; i <= max; i++) { if (i % 3 === 0) { if (i % 5 === 0) { result += "FizzBuzz"; } else { result += "Fizz"; } } else { if (i % 5 === 0) { result += "Buzz"; } else { result += i; } } result += "\n"; } return result; }
// 関数型プログラミング function fizzbuzz(max) { return _.map(_.range(1, max+1), function(val) { return val % 3 === 0 ? (val % 5 === 0 ? "FizzBuzz" : "Fizz") : (val % 5 === 0 ? "Buzz" : val); }).join("\n"); }
この2つの関数はどちらも FizzBuzz 問題の解答の文字列を返す関数ですが、言語が同じでも、指向の違いでこれほどまでにコードが変わるというのは興味深いです。2つのコードの違いを3つ書き出してみます。
① 評価する式の順序
命令型プログラミングでは、上から下へと式が評価されます。それに対して関数型プログラミングでは、ぱっと見だとどの式から評価されるかがわかりません。
② 変数を宣言しているか
命令型プログラミングでは、2つの変数を宣言しています。解答の文字列を格納する変数と、ループ変数です。関数型プログラミングでは、変数は宣言されていません。
③ ループに for 文を使用しているか
関数型プログラミングでは、ループに _.range メソッドと _.map メソッドが使われています。(これらのメソッドは Underscore.js で定義されています)
2つのプログラミング手法の違いを知ることは楽しいです。引き続き、関数型プログラミングについて勉強していきます。