Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save akm/fffc81b1cdf06b26c55557f5114a77fa to your computer and use it in GitHub Desktop.
Save akm/fffc81b1cdf06b26c55557f5114a77fa to your computer and use it in GitHub Desktop.
## ねらい
プログラミング言語の習得に取り掛かろうとすると腰が重くなることってありますよね。
しかし、種類の異なるプログラミング言語を理解して、その長所を理解すると、元々知っていた言語でのプログラミングについての理解が深まることもあります。
ここでは普段DelphiやAIRを使っている皆さんの視界をちょっとだけ広げるために、いくつかの言語の特徴をお話します。
## Erlang
携帯電話とかを作っているエリクソンが作った言語です。
https://www.erlang.org/
https://ja.wikipedia.org/wiki/Erlang
## 再代入がない
正確にはErlangでは変数に値を設定することを`代入` ではなく `束縛` と言います。
一度束縛された変数は、二度と束縛できず、そのように書くとコンパイラに怒られます。
## for文がない
再代入ないのでよくあるfor文は無理なんですが、erlangにはそもそも繰り返しがありません。
繰り返しは再帰呼出しで実現します。
```erlang
sum(0) -> 0;
sum(N) -> N + sum(N-1).
```
```
1> ls().
db.erl list.erl new_list.erl
print_number.erl sort.erl sum.beam
sum.erl
ok
2> c(sum).
{ok,sum}
3> sum:sum(10).
55
4> halt().
```
## スレッドがない
Erlangはアクターモデルという仕組みを採用しており、プログラムはErlang固有の`プロセス` という単位で実行されます。
これはOSのプロセスとは別ものです。このプロセスはそれぞれが独立したコンピュータのようなもので、
メッセージを非同期通信することで、協調動作することができます。
erlangのプロセスは、それぞれ独立したメモリを持っているので、スレッドを使うときのように
メモリを共有することもないので非常に安全です。
## なんでも比較可能
よくあるプログラミング言語では型が同じデータしか比較できませんが、Erlangではすべての型の間で順序があるので、可能です。
```
number < atom < reference < fun < port < pid < tuple < map < nil < list < bit string
```
http://erlang.org/doc/reference_manual/expressions.html#term-comparisons
## Ruby
まつもとゆきひろさんが作ったオブジェクト指向言語です。
https://www.ruby-lang.org/ja/
## 実行環境
rbenvはrubyの環境のインストールや切り替えを行うツールです。 `rbenv install -l` でインストール可能な実行環境とそのバージョンを取得できます。
```
$ rbenv install -l
Available versions:
1.8.5-p52
1.8.5-p113
1.8.5-p114
1.8.5-p115
1.8.5-p231
1.8.6
1.8.6-p36
1.8.6-p110
(snip)
rbx-3.100
ree-1.8.7-2011.03
ree-1.8.7-2011.12
ree-1.8.7-2012.01
ree-1.8.7-2012.02
topaz-dev
```
環境何種類あるのか調べてみましょう。
```bash
$ rbenv install -l | ruby -e 'puts ARGF.map{|line| line.split("-",2).first}.reject{|s| s =~ /\d\.\d\.\d+/}.uniq'
Available versions:
jruby
maglev
mruby
rbx
ree
topaz
```
よく使われるのは、Cで作られたC Rubyですが、用途によって使い分けます。
Javaのライブラリ等を使いたい場合はjruby、組み込み系ならばmrubyという風に。
## JVM言語
JVM = Java Virtual Machine
Javaのスローガン [Write once, run anywhere](https://ja.wikipedia.org/wiki/Write_once,_run_anywhere)を実現するために、OSなどの違いを吸収するために作られたプログラムの実行環境。
Javaのソースコードは、Javaバイトコードと呼ばれるJVM用の中間形式にコンパイルされる。JVMはJavaバイトコードを解釈して動作します。
JRubyやScala、今流行りのKotlinもJVM言語。
https://en.wikipedia.org/wiki/List_of_JVM_languages
Javaと同じコンパイルするとJavaバイトコードを生成するものもあれば、実行時に解釈するスクリプト言語もあります。
JVMは後方互換性をきっちり守り続けているので、実行環境として安定してます。
https://www.slideshare.net/yyyank/jvmjava-69784006
## ECMAScript
[ECMAScript](https://ja.wikipedia.org/wiki/ECMAScript) は様々なアプリケーションでサポートされており、実はAdobe FlashのActionScriptもECMAScriptの一つでした。
2018年現在では、ECMAScriptと言えば、 6th Edition ECMAScript 2015 以降を指すことが多く `ES5` `ES6` などと言われることが多いと思います。
### Arrow Functions
ES6から使えるようになった[アロー関数](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/arrow_functions)は従来の関数定義とは大きく異なります。
従来はfunctionというキーワードを使っていました。
```javascript
function foo(a, b, c) {
return a + b * c;
}
```
しかしこれを以下のように書くことができます。
```javascript
const foo = (a,b,c) => a + b * c
```
```javascript
const foo = (a,b,c) => {
return a + b * c;
}
```
ちなみに [const](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/const) は再代入、再宣言不可の変数を宣言しています。
## 高階関数
> 高階関数(こうかいかんすう、英: higher-order function)とは、第一級関数をサポートしているプログラミング言語において、関数(手続き)を引数にしたり、あるいは関数(手続き)を戻り値とするような関数のことである。
https://ja.wikipedia.org/wiki/高階関数
高階関数は様々な言語で使うことができるが、ここではJavaScript(ECMA Script6)で書いてみます。
Chromeで [表示]-[開発/管理]-[デベロッパーツール]を開きます。
例えばさっきのワンライナーをES6で書いてみます。
```javascript
const text = ` 1.8.5-p52
1.8.5-p113
1.8.5-p114
1.8.5-p115
1.8.5-p231
1.8.6
1.8.6-p36
1.8.6-p110
(snip)
rbx-3.100
ree-1.8.7-2011.03
ree-1.8.7-2011.12
ree-1.8.7-2012.01
ree-1.8.7-2012.02
topaz-dev`
const lines = text.split("\n");
const lineToPrefix = (line) => line.split("-")[0];
const notOnlyNumber = (s) => !s.match(/\d\.\d\.\d+/);
const uniq = (acc, v) => {
if (!acc.includes(v))
acc.push(v)
return acc
}
lines.map(lineToPrefix).filter(notOnlyNumber).reduce(uniq, [])
```
map, filter, reduceは引数に関数を取り、配列を操作する高階関数。
実はこれらは配列の操作の基本中の基本。
- map
- 写像
- 配列の各要素をそれぞれ何かに変換した新しい配列を作る
- filter
- 選択
- 配列の各要素について、評価を行い、合致した要素からなる新しい配列を作る
- reduce
- 畳み込み
- 配列の各要素とaccumulatorを処理し、配列全体から一つの値を返す
- 例: 整数の配列から合計値を得る
- `[1,2,3,4,5].reduce((acc, val) => acc + val)`
配列操作はこれらを組み合わせることで、何でも出来ます。
### 関数を返す関数
例えば、上の `lineToPrefix` では `"-"` がハードコーディングされていますが、これを変更できるように引数にしたいとします。
しかし、単純に `lineToPrefix` の引数として追加してしまったら `map` が期待する関数の形と違ってしまいます。
```javascript
const lineToPrefix = (line, separator) => line.split(separator)[0];
// ...
lines.map(lineToPrefix) // <= separatorが渡されない
```
`map` が期待する関数の形を変えないように、separatorを指定できるようにするために関数を返す関数(=高階関数)を使います。
```javascript
const lineToPrefixFunc = (separator) => {
return (line) => line.split(separator)[0];
}
const lineToPrefix = lineToPrefixFunc("-")
// ...
lines.map(lineToPrefix) // <= separatorが渡されている
```
`lineToPrefixFunc` は `lineToPrefix` になる関数を作って返す関数です。
説明のために2つに分けて書きましたが、いちいち分けるのは結構面倒くさいので、まとめて書きます。
```javascript
const lineToPrefix = (separator) => {
return (line) => line.split(separator)[0];
}
// ...
lines.map(lineToPrefix("-"))
```
`lineToPrefix` は一行しかないアロー関数なので、もっと短くできます。
```javascript
const lineToPrefix = (separator) => (line) => line.split(separator)[0];
// ...
lines.map(lineToPrefix("-"))
```
### map, filter, resultを使わない関数
```javascript
const text = ` 1.8.5-p52
1.8.5-p113
1.8.5-p114
1.8.5-p115
1.8.5-p231
1.8.6
1.8.6-p36
1.8.6-p110
(snip)
rbx-3.100
ree-1.8.7-2011.03
ree-1.8.7-2011.12
ree-1.8.7-2012.01
ree-1.8.7-2012.02
topaz-dev`
const extractPrefix = (lines) => {
const result = []
lines.forEach((line) => {
const prefix = line.split("-")[0];
if (!prefix.match(/\d\.\d\.\d+/)) {
if (!result.includes(prefix)) {
result.push(prefix)
}
}
})
return result;
}
extractPrefix(text.split("\n"));
```
### 関数を分ける理由
引数だけが戻り値に関係する関数を純粋関数と言うが、純粋関数はテストしやすい。
ちゃんと設計することで、それぞれの関数をテストしさえすれば、全体について複雑なテストをたくさん行う必要がなくなることも多い。
例えば、前述の `lineToPrefix`, `notOnlyNumber`, `unique` でそれぞれ以下のケースをテストする必要があるとしよう。
- `lineToPrefix`
- 正常な場合
- 空文字列の場合
- `-` を含まない場合
- `-` で始まる場合
- `notOnlyNumber`
- (整数).(整数).(整数) の場合
- (整数).(整数).(整数) ではない場合
- `unique`
- 重複が1つもない場合
- 一つの要素だけ重複が1個だけある場合
- 一つの要素だけ重複が複数個ある場合
- 複数個の要素だけ重複が1個だけある場合
- 複数個の要素だけ重複が複数個ある場合
それぞれテストすれば 4 + 2 + 5 = 11 個のテストで済みます。
しかし、これと同等のことを `extractPrefix` について行おうとすると、
4 * 2 * 5 = 40個 ものケースが必要になる可能性があります。
どちらのテストの方がコストがかからないかは一目瞭然と言えるでしょう。
また、関数が分かれているということは、それぞれの責務が明確になりやすいので、コードも読みやすくなります。
大きくごちゃごちゃして何をやっているのかがよくわからない関数より、数は多いかもしれないけど個々の役割が
ハッキリしている方がメンテナンスも楽です。
### MapReduce
またmapは並列処理に向いているので、大量のデータを処理する際には、map操作をいかに見つけるかが肝要です。
できるだけ配列全体が必要となるreduceで処理するものを減らし、map側で処理できるように設計できると、並列処理を行う際に有利になります。
これを分散システムと組み合わせることで大量の処理を短い時間で実行するものが[MapReduce](https://ja.wikipedia.org/wiki/MapReduce)です。
実装としては [BigTable](https://ja.wikipedia.org/wiki/BigTable) や [Hadoop](https://ja.wikipedia.org/wiki/Apache_Hadoop) が知られています。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment