Lambda 関数に俺はなる

この記事は [Vim Advent Calendar 2017](https://qiita.com/advent-calendar/2017/vim) の25日、最終日の記事です。[その1](https://qiita.com/advent-calendar/2017/vim)、[その2](https://qiita.com/advent-calendar/2017/vim)、多くの方に参加頂きました。参加頂いた皆様、本当にご苦労様でした。そして完走おめでとうございます。

# 今年のおさらい

今年は Vim の国際カンファレンス [Vimconf2017](http://vimconf.vim-jp.org/2017/) も開催され、未だ衰えを知らない Vim の人気を再認識する年になったと思っています。

Vim バージョン 8.0 がリリースされたのが去年の9月の事。約1年くらいですが、最近になってようやく皆さんに認知されてきたのかなとも思います。Vim 8.0 の新機能を今一度おさらいしておくと、大きくは以下の物がありました。

* チャネルによる非同期 I/O のサポート (Channel)
* ジョブ (Job)
* タイマー (Timer)
* パーシャル (Partial)
* ラムダ (Lambda)、クロージャ (Closure)
* パッケージ (Packages)

引用: http://vim-jp.org/blog/2016/09/13/vim8.0-features.html

今考えると、これらが無い頃に Vimmer はいったいどうやって Vim script を書いていたんだろうと思うくらい重要な機能が入ったと思っています。

# Lambda は凄い

特に僕が気に入っているのが Lambda です。これは元々、僕が `sort` 関数の引数で比較演算の指定方法が面倒で、どうしても欲しくて作り始め、さらに kaoriya さんや vim-jp の皆さん、そして
 k-takata さんに後押しして貰って入れて貰いました。

例えば辞書の配列を `sort` するには以下の様に書く必要がありました。

```vim
function! s:by_name(lhs, rhs)
  return a:lhs.name < a:rhs.name
endfunction

let foo = [
\ {"name": "alice", "age": 16},
\ {"name": "john", "age": 14},
\ {"name": "bob", "age": 13},
\]
echo sort(foo, function('s:by_name'))
```

比較処理を書くだけの為にいちいち関数を定義しないといけなかったのです。これが Lambda を使うと以下の様に簡単に書く事が出来るのです。

```vim
let foo = [
\ {"name": "alice", "age": 16},
\ {"name": "john", "age": 14},
\ {"name": "bob", "age": 13},
\]
echo sort(foo, {lhs, rhs -> lhs.name < rhs.name})
```

Lambda は式しか書けません。例えば変数に値を代入したり `echo` コマンドなど、文は直接書けないのです。ただし方法がない訳ではありません。例えばタイマーを使い、1秒に1回、時刻を表示するのであれば以下の様に `execute()` 関数を使う事で実現できます。

```vim
let s:timer = timer_start(1000, {t ->
\ execute('echo strftime("%c", localtime())', '')},
\ {'repeat': -1})
```

さらにカウントアップする数字を表示するのであれば以下の様に List を使います。

```vim
let s:count = 0
let s:timer = timer_start(1000, {t -> [
\   execute('let s:count += 1'),
\   execute('echo s:count', ''),
\ ]}, {'repeat': -1})
```

Lambda と `execute()` 関数があればつまりはなんでも出来る訳です。

# Lambda でもやれる

例えば List から Dict を作りたい時、皆さんはどうやっているでしょうか？ `['key1', 'value1', 'key2', 'value2', ...]` から `{'key1': 'value1', 'key2': 'value2', ...}` を作るといった場合です。関数を作りますか？いえ、出来るんです。Lambda でも。

```vim
let l = [['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']]
echo call({x,y-> [map(x, 'execute("let y[v:val[0]]=v:val[1]")'),y]}, [l,{}])[1]
```

`call()` 関数は第一引数に関数、第二引数に関数への引数を List で渡します。今回であれば対象の List と空の Dictionary `{}` を渡しています。Lambda 関数の引数 x には List が、y には `{}` が格納されます。`map()` 関数は List と Dictionary のどちらでも引数として取れますが、戻り値も同じ型が返ります。つまり List として引数に与えた場合は `map()` および `call()` は List を返します。そこで List と空の Dictionary を双方保持する List をコンテナとして引数として与え、`call()` の戻り値から `[1]` で Dictionary 側を参照しているのです。

簡単ですね。

# Lambda でスパイラル

例えば以下の様に、外側から数字が小さくなりつつ真ん中で終了する図形を描くにはどうすれば良いでしょうか？

```
99  98  97  96  95  94  93  92  91  90
64  63  62  61  60  59  58  57  56  89
65  36  35  34  33  32  31  30  55  88
66  37  16  15  14  13  12  29  54  87
67  38  17   4   3   2  11  28  53  86
68  39  18   5   0   1  10  27  52  85
69  40  19   6   7   8   9  26  51  84
70  41  20  21  22  23  24  25  50  83
71  42  43  44  45  46  47  48  49  82
72  73  74  75  76  77  78  79  80  81
```

出典: [Challenge - Print Spiral](https://dev.to/heikodudzus/challenge---print-spiral-6kg)

出来ます！Lambda でも。

```vim
echo join(call({n->map(range(n),{i,x->join(map(range(i*n,i*n+n-1),{->printf("%*i ",float2nr(ceil(log10(n*n-1))),call(function({...->a:1(a:1,a:2,a:3,a:4)},[{e,n,x,y->n%2==0?(y==0?n*n-1-x:(x==n-1?n*n-n-y:e(e,n-1,x,y-1))):(y==n-1?n*n-n+x:(x==0?n*n-n-(n-1)+y:e(e,n-1,x-1,y)))}]),[n,v:val%n,v:val/n]))}),' ')})},[10]),"\n")
```

![スパイラル](https://qiita-image-store.s3.amazonaws.com/0/665/38d4c0c2-52a9-37cf-1cf4-b13273c3c2de.png)

作り方はそれほど難しくありません。まず上記 dev.to のリンク先にあるコードをそのまま Vim script に移植します。

```vim
function! s:e(n,x,y)
  let sq = a:n * a:n
  if a:n%2 == 0
    if a:y == 0
      return sq - 1 - a:x
    endif
    if a:x == a:n-1
      return sq - a:n - a:y
    endif
    return s:e(a:n-1,a:x,a:y-1)
  endif
  if a:y == a:n-1
    return sq - a:n + a:x
  endif
  if a:x == 0
    return sq - a:n - (a:n - 1) + a:y
  endif
  return s:e(a:n-1,a:x-1,a:y)
endfunction

let n = 10
let l = float2nr(ceil(log10(n * n-1)))
for y in range(n)
  for x in range(n)
    echon printf("%*i ", l, s:e(n,x,y))
  endfor
  echo ""
endfor
```

次に関数を Lambda に、そして if 文を三項演算子に置き換えます。

```vim
let s:e = {n,x,y->
\  n%2 == 0 ? (
\    y == 0 ?
\	   n * n - 1 - x
\    : (
\      x == n-1 ?
\        n * n - n - y
\      :
\        s:e(n-1,x,y-1)
\    )
\  ) : (
\    y == n-1 ?
\      n * n - n + x
\    : (
\      x == 0 ?
\        n * n - n - (n - 1) + y
\      :
\        s:e(n-1,x-1,y)
\    )
\  )
\}

let n = 10
let l = float2nr(ceil(log10(n * n-1)))
for y in range(n)
  for x in range(n)
    echon printf("%*i ", l, s:e(n,x,y))
  endfor
  echo ""
endfor
```

ここで1つ問題が発生します。Lambda 関数を格納した変数 `s:e` は再帰呼び出しを行う必要があります。つまり変数を無くすという事は再帰呼び出しが出来なくなるのです。

そこで登場するのが Vim 8.0 で入った Partial です。

```vim
let X = function('s:foo', [1,2,3])
```

Partial を使うと `X()` という呼び出しの際に `s:foo` に引数 1, 2, 3 を渡す事が出来ます。つまり部分引数として Lambda を渡す事で、`function()` の第一引数で与える関数の引数としてバインドできる事になります。

```vim
let s:e = function({...->a:1(a:1,a:2,a:3,a:4)}, [{e,n,x,y->
\  n % 2 == 0 ? (
\    y == 0 ?
\	   n * n - 1 - x
\    : (
\      x == n-1 ?
\        n * n - n - y
\      :
\        e(e, n-1, x, y-1)
\    )
\  ) : (
\    y == n-1 ?
\      n * n - n + x
\    : (
\      x == 0 ?
\        n * n - n - (n - 1) + y
\      :
\        e(e, n-1, x-1, y)
\    )
\  )
\}])

let n = 10
let l = float2nr(ceil(log10(n * n-1)))
for y in range(n)
  for x in range(n)
    echon printf("%*i ", l, s:e(n,x,y))
  endfor
  echo ""
endfor
```

これで `s:e` が消せます。あとは for 文で回している部分を range と map で実装し直せば、上記の様に1行でスパイラル図形が表示できるという訳です。

また最後の方にある 10 を大きくすれば、お好きな N x N の図形が表示されます。

簡単ですね。

# Lambda で中華料理画像うｐよろしく

こんな図形はどうでしょうか？

```
中華料理画像うｐよろしく
華＠＠＠＠＠＠＠＠＠＠し
料＠＠＠＠＠＠＠＠＠＠ろ
理＠＠＠＠＠＠＠＠＠＠よ
画＠＠＠＠＠＠＠＠＠＠ｐ
像＠＠＠＠＠＠＠＠＠＠う
う＠＠＠＠＠＠＠＠＠＠像
ｐ＠＠＠＠＠＠＠＠＠＠画
よ＠＠＠＠＠＠＠＠＠＠理
ろ＠＠＠＠＠＠＠＠＠＠料
し＠＠＠＠＠＠＠＠＠＠華
くしろよｐう像画理料華中
```

出典: https://anond.hatelabo.jp/20161015194043

出来ます。Lambda かわいいよ。

```
echo call({s->call({s,u->join([s]+map(range(2,len(u)-1),{i->u[i+1].repeat("＠",len(u)-2).u[-i-2]})+[join(reverse(u),'')],"\n")}, [s, split(s,'\zs')])}, ['中華料理画像うｐよろしく'])
```

![中華料理画像うｐよろしく](https://qiita-image-store.s3.amazonaws.com/0/665/7a0f754f-8f34-b7b3-ec67-42c8f9347e2f.png)

簡単ですね。

# Lambda でカレンダー

UNIX で `cal` コマンドを実行すると以下の様に表示されます。

```
Su Mo Tu We Th Fr Sa
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
```

これは難しいでしょうか？いえ、出来ます。Lambda なら。

```vim
echo
\ "Su Mo Tu We Th Fr Sa\n"
\ .join(split(
\  repeat('   ', strftime('%w', localtime() - (strftime('%d', localtime())-1)*60*60*24))
\  .join(map(range(1,
\     call(
\       {y,m->
\         [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m-1] + ((m == 2 && y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? 1 : 0)
\       }, [strftime(strftime('%y', localtime())), strftime(strftime('%m', localtime()))]
\     )), {_,x->printf('%02d', x)}), ' '), repeat('.', 21).'\zs'), "\n")
```
うるう年の計算も入っていて安心ですね。見やすくする為に `\` を使って改行していますが、1行にすると以下の様になります。

```vim
echo "Su Mo Tu We Th Fr Sa\n".join(split(repeat('   ', strftime('%w', localtime() - (strftime('%d', localtime())-1)*60*60*24)).join(map(range(1,call({y,m->[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m-1] + ((m == 2 && y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? 1 : 0)}, [strftime(strftime('%y', localtime())), strftime(strftime('%m', localtime()))])), {_,x->printf('%02d', x)}), ' '), repeat('.', 21).'\zs'), "\n")
```
実行してみましょう。

![カレンダー1](https://qiita-image-store.s3.amazonaws.com/0/665/acd2aa8a-c350-e7f4-cacc-3a382157b077.png)

![カレンダー2](https://qiita-image-store.s3.amazonaws.com/0/665/cce097bc-69c7-41ea-1242-370aaaf37218.png)

いちいち Vim を起動するの面倒だという人は、シェルで以下を実行すればいいのです。

```bash
$ vim --not-a-term --cmd 'echo "Su Mo Tu We Th Fr Sa\n".join(split(repeat('\''   '\'', strftime('\''%w'\'', localtime() - (strftime('\''%d'\'', localtime())-1)*60*60*24)).join(map(range(1, call({y,m->[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m-1] + ((m == 2 && y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? 1 : 0)}, [strftime(strftime('\''%y'\'', localtime())), strftime(strftime('\''%m'\'', localtime()))])), {_,x->printf('\''%02d'\'', x)}), '\'' '\''), repeat('\''.'\'', 21).'\''\zs'\''), "\n")' --cmd q > /dev/null
```

![カレンダー3](https://qiita-image-store.s3.amazonaws.com/0/665/74b043c2-7fcb-f4de-501c-2c1dcb45fc64.png)

簡単ですね。

# Lambda があれば強くなれる

この様に、Lambda があれば Vim script は強くなれるのです。綺麗な図形を1行で書いて同僚に見せれば、皆が貴方を見る目も変わるはずです。みなさんもぜひ Lambda やりましょう。いや、して下さい。そして Lambda になりましょう。