はじめに
年末年始でヒマなので、Haskell ライクな局所的な変数束縛や関数に対してのみ有効な変数束縛を R で実現して遊ぼうというのをやってみました。
動機
何かカッコいいじゃないですか、こういうの。
velocity x1 x2 t1 t2 = dx / dt
where
dx = x2 - x1
dt = t2 - t1
```
変数の順番考えずに定義だけ並べて宣言的に書けるのとか。スコープがきっちり限定されるのとか。
あと[`lambda.r`パッケージ](https://github.com/zatonovo/lambda.r)を見て楽しそうだなと思ったのとか、ですね。
実装
---
[https://github.com/ngr-t/r-bindedEval](https://github.com/ngr-t/r-bindedEval) に置いてます。一丁前にパッケージにしてみたのはパッケージの作り方の勉強も兼ねてです。
使い方
---
Haskell の let 式の真似をした let 関数はこんな感じです。Haskell 連呼するのって Haskell ガチ勢に殴られそうで怖いですね。
適当に束縛したい変数とその定義を並べて、`.in`引数に最終的に評価したい表現を渡します。
```R
z <- let(x = 1, y = 2, .in = x + y)
# x と y には let 関数の外からはアクセスできない
velocity_let <- function (x1, x2, t1, t2) let(
dx = x2 - x1,
dt = t2 - t1,
.in = dx / dt)
velocity_let(80, 90, 0, 0.5)
# [1] 20
```
もしかすると
```R
z <- let(x = 1, y = 2) %.in% (x + y)
```
みたいな構文の方がそれっぽいかもしれません。そのうちこっちで書いてみるかもです。
`where`構文の真似っこは以下です。パイプを使って書いていますがこちらは関数を第一引数にします。関数の定義の際に使ってください。
```R
velocity_where <- (function (x1, x2, t1, t2) {
dx / dt
}) %>%
where(
dx = x2 - x1,
dt = t2 - t1)
velocity_where(80, 90, 0, 0.5)
# [1] 20
```
関数定義を`()`で括るのはちょっとイケてないのですが、そこは R の演算子の結合順序の関係で、そうしないと関数ではなくて`{}`のでくくった部分が`where()`関数の引数になってしまいます。
`let()` および `where()` 関数両方についてですが、束縛される変数の定義にほかの束縛された変数を使えます。順番は適当でも大丈夫です。なので
```R
let(x = y * 2, y = 1, z = x * y, .in = z)
# [1] 2
```
とかでも問題ありません。
以下のように相互参照してるのはダメです。
```R
let(x = y, y = x, .in = x + y)
# Error
```
今回の実装ではこの辺りは`igraph::topological.sort()`で評価順序を決めているんですが、`delayedAssign()`を使えば、こちらで難しいことを考えなくても R のインタープリタが必要に際してよしなに評価してくれます(あらかた書いたところでこの関数の存在を知りました)。
関数を束縛しても良いんですよ。
```R
fire <- (function (x, w, threshold)
heaviside(weighted_sum - threshold)) %>%
where(
heaviside = function (x) ifelse(x > 0, 1, 0),
weighted_sum = sum(x * w))
```
全体的にありがたみの薄い例しか挙げられませんでしたね。
パターンマッチング
---
無茶言わないでください。
所感
---
R はキモいDSLが書きやすいですね、良くも悪くも。環境操作とか構文パースとか評価順序いじったりとか、いろいろ出来ますほんと。
パッケージ作るのは思っていたほどには面倒ではありませんでした。`devtools::build()` が静的言語解析?をやってくれるのも助かりますね。