R
関数型言語
関数型プログラミング
RDay 8

R Advent Calendar 2014: Rでfunctional programming!?

More than 3 years have passed since last update.


これはなにか?

QiitaのR Advent Calendar 2014 ( http://qiita.com/advent-calendar/2014/r-rstudio )の8日目です。ATNDにあるやつの方が栄えているようです( https://atnd.org/events/58648 )。


ちまたで関数型言語が大ブーム

関数型言語、はやってますね。関数プログラミング実践入門などによると、並列処理や高度な抽象化(コードが短かくなる)などの観点から注目が高まっているようです。本を買うのが大好きな私は関数型プログラミングの本だけで何冊も買いました。当然ほとんど読んでいません。

何を持って関数型言語というのかについては意見が別れるようですが、古典的には関数が値として自在に扱えること、最近はimmutability (破壊的再代入やmutationを許容しない、つまり状態(state)変化の抑止)などがいわれているようです。ML系言語(Standard ML, OCamlなど。HaskellやF#もこの流れをくむ?)では、静的型付け(static typing)/型推論(type inference)も話題になりますが、動的型付け(dynamic typing)のClojureも関数型言語に分類されていることが多いので、関数型言語の必要条件ではないようです。

私は今年はClojureとStandard MLをすこし勉強しました。前者は4clojure ( https://www.4clojure.com )とFunctional Programming in Clojure ( http://mooc.cs.helsinki.fi/clojure )が参考になりました。Boston ClojureやTokyo.cljにも行ってみました。lispは文法がつるっとした感じがしていいですね。後者はCourseraでやっていたProgramming Languages ( https://www.coursera.org/course/proglang )というコースの一環でやりました。RとEmacs lispが主要言語の私は、静的型付けも型推論もどちらも初めてだったので、おもしろい経験でした。まあ、どちらも私の能力では実用からはほど遠いですが。


R言語は関数型言語なのか?

で、私たちの愛してやまないR言語は関数型言語なのでしょうか? 古典的な関数を自由に扱えるという点は満しそうです。下記のごとく無名関数はfunctionというclassを持つ一級オブジェクトで、関数定義は無名関数本体を代入しているだけです。(無名)関数を引数として使う高階関数(higher-order function)は、下記の例がRっぽいかは別としてlapplyやsapplyなどで一般的です。無名関数本体を値として返すのも、関数定義時の環境(下記の例ではn = 3)を持ち歩く(環境と関数をあわせてclosureと呼ぶ)のも普通です。

## 無名関数は"function"クラス

class(function(x){x*x})
## [1] "function"

## そのまま使える
(function(x){x*x})(3)
## [1] 9

## 関数定義は無名関数本体を代入しているだけ
square = function(x){x*x}
square(3)
## [1] 9

## 無名関数本体を高階関数sapplyの引数として利用
sapply(1:10, function(x){x*x})
## [1] 1 4 9 16 25 36 49 64 81 100

## 無名関数を返す関数
addN = function(n) {
function(x) {
x + n
}
}
## n = 3の環境を持ち歩く無名関数をadd3に代入
add3 <- addN(n = 3)
## 高階関数sapplyの引数として利用
sapply(1:10, add3)
## [1] 4 5 6 7 8 9 10 11 12 13

状態変化を抑止するimmutabilityについてはどうでしょうか? 破壊的再代入はいくらでもできます。というか、interactiveに使うことが多い統計解析言語であるRでは、これを抑止するのは厳しいでしょう。定義されたオブジェクトの再代入以外の直接のmutationはほぼないです。下記で110..100のaを破壊的再代入以外の方法でソートする方法はないと思います。Rで育った私は、Pythonでa.sort() (リストaのソートメソッド呼び出し)で、aを直接mutationさせるのを見たときはちょっとびっくりしました。ちなみに、Rでも普通のobject oriented programmingっぽいreference class (R5 classとか言われていたもの)を使うと可能なようです。まあ、この微妙なimmutability (破壊的再代入は許すけど、何かするたびに元のオブジェクトを傷付けないためにコピーが発生する)がRのパフォーマンスの悪さの元凶なようですが。

## 破壊的再代入

a <- 10
a <- "hello"
a <- 110:100
a[1] <- 111 # これをmutationととるのか破壊的再代入ととるのか不明?
a
## [1] 111 109 108 107 106 105 104 103 102 101 100

## 関数によるオブジェクトのmutationはない
sort(a)
## [1] 100 101 102 103 104 105 106 107 108 109 111

a
## [1] 111 109 108 107 106 105 104 103 102 101 100

## bはaのコピーなので、以降aをいじっても影響を受けない
b = a
a <- "a has been changed"
b
## [1] 111 109 108 107 106 105 104 103 102 101 100

ということで、R言語が関数型言語かどうかは定義によるという微妙な結論なようです。


Rで関数型言語っぽい表現をする

あまり役にたたないはなしはここまでにして、関数型言語でみかける表現をRでも使ってみたいと思います。

関数合成(functional composition)

連続して適用する複数の関数をまとめて、一つの関数にします。数学的には下記のごとくでしょうか。最近良くみかけるpipelineに似ていますが、関数合成では返ってくるのは関数なので名前をつければ再利用できます。

$$h(g(f(x))) = (h \circ g \circ f) (x)$$

あまり意味がないですが、一連の数字のsquare sumをnegativeにする関数を定義してみます。Standard MLではo(小文字のO)をつかいます。下から三番目が関数合成です、数学的定義そのままで、右から読みます。

(* 合成する関数を定義。ここはあまり気にしない *)

fun square xs =
case xs of
[] => []
| x::xs' => (x*x)::(sq xs')
fun sum xs =
case xs of
[] => 0
| x::xs' => x + (sum xs')
fun negate x =
~x
(* これが関数合成の部分 *)
val neg_sq_sum = negate o sum o square
(* 実際使用 *)
neg_sq_sum [0,1,2,3,4,5,6,7,8,9,10]
(* 結果 : val it = ~385 : int *)

Rではfunctionalというパッケージがあり、そのなかにComposeという関数合成のための関数があります。左から右になっているのが、数学的にはちょっといまいちな気がしますが、実際は読みやすいかもしれません。引数がひとつの関数しか使えないようです。多少使えるかもしれない例としては、ときどき使うcoef(summary(regressionObject))専用のCoefTableという関数を定義してみました。

library(functional)

negate = function(x) {-x}
## これが関数合成の部分
NegSqSum = Compose(square, sum, negate)
NegSqSum(0:10)
## [1] -385

## 多少実用的な例
CoefTable = Compose(summary, coef)
CoefTable(lm(Sepal.Length ~ Petal.Length + Species, data = iris))
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.6835266 0.10609608 34.718780 1.968671e-72
## Petal.Length 0.9045646 0.06478559 13.962436 1.121002e-28
## Speciesversicolor -1.6009717 0.19346616 -8.275203 7.371529e-14
## Speciesvirginica -2.1176692 0.27346121 -7.743947 1.480296e-12

部分適用(partial application)

複数の引数を取る関数のいくつかを固定した状態の関数を返すことです。Clojureだとpartialという関数を使います。下記ではかけ算の"*"関数の一番目の引数として10を適用して固定した関数を定義して、times10という名前にしています。times10に9を与えれば10倍にする関数ですので90と評価されます。

;; "*"関数に最初の引数として10を与えてtimes10関数とする

(def times10 (partial * 10))
;; times10関数に9を引数としてあたえる
(times10 9)
;; => 90

Rでは同じくfunctionalにCurry()という関数があります。名前は厳密には間違っている(curry化という言葉の誤用)のですが、しっかり部分適用してくれます。実用的な例としては複数の引数を取る関数で、一個だけ引数を変化させながらくりかえすという状況がたまにあると思います。ここでは、irisデータセットがモデル式をあたえるだけでいつでも分析できる専用関数lmIrisを定義してみました。data引数をirisに固定していformula引数を残してあるわけです。便利ですね。2014-12-26追記Hadley Wickhamのpryrパッケージにpartial()という正しい名前の部分適用関数がありました。

## "*"関数に最初の引数として10を与えてtimes10関数とする

times10 = Curry("*", 10)
## times10関数に9を引数としてあたえる
times10(9)
## [1] 90

## formulaをあたえるだけでirisデータセットの解析をする専用関数
lmIris = Curry(lm, data = iris)
resIris = lmIris(Sepal.Length ~ Petal.Length + Species)
## 結果オブジェクトに先程のCoefTableを使用
CoefTable(resIris)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.6835266 0.10609608 34.718780 1.968671e-72
## Petal.Length 0.9045646 0.06478559 13.962436 1.121002e-28
## Speciesversicolor -1.6009717 0.19346616 -8.275203 7.371529e-14
## Speciesvirginica -2.1176692 0.27346121 -7.743947 1.480296e-12

その他

Rの関数リテラルが長いということでhoxo_mさんが簡略な記載のパッケージを作成されていました(http://d.hatena.ne.jp/hoxo_m/20141204/p1 )。括弧が少なくなりますね。

## 通常

function(x) x*x
## function(x) x*x
(function(x) x*x)(3)
## [1] 9

## hoxo_mさん
devtools::install_github("hoxo-m/lambdaR")
library(lambdaR)
lambda(x: x*x)
## function(x) x * x
lambda(x: x*x)(3)
## [1] 9

ML系で見掛けるpattern matchingも関数型言語の必要条件ではなさそうですが、関数型言語の文脈でよく出てきます。lambda.rというパッケージ(前述とは別もの; https://github.com/zatonovo/lambda.r )で使用できます。実用的かは微妙かも。このパッケージは他にもいろいろできそうです。

library(lambda.r)

fib(0) %as% 1
fib(1) %as% 1
fib(n) %as% {fib(n-1) + fib(n-2)}
sapply(0:10, fib)
## [1] 1 1 2 3 5 8 13 21 34 55 89

手習いlisperとしてはifelse()のネストを見ると辛くなります。そんなときは cond ですよね。どっかにないかと思ったらgistにころがっていました(https://gist.github.com/DASpringate/8595464 )。

FizzBuzz <- function(x) {

cond(x %% 15 == 0, "FizzBuzz",
x %% 3 == 0, "Fizz",
x %% 5 == 0, "Buzz",
true = x)
}
sapply(1:100, FizzBuzz)
## [1] "1" "2" "Fizz" "4" "Buzz" "Fizz" "7" "8" "Fizz" "Buzz"
## [11] "11" "Fizz" "13" "14" "FizzBuzz" "16" "17" "Fizz" "19" "Buzz"
## [21] "Fizz" "22" "23" "Fizz" "Buzz" "26" "Fizz" "28" "29" "FizzBuzz"
## [31] "31" "32" "Fizz" "34" "Buzz" "Fizz" "37" "38" "Fizz" "Buzz"
## [41] "41" "Fizz" "43" "44" "FizzBuzz" "46" "47" "Fizz" "49" "Buzz"
## [51] "Fizz" "52" "53" "Fizz" "Buzz" "56" "Fizz" "58" "59" "FizzBuzz"
## [61] "61" "62" "Fizz" "64" "Buzz" "Fizz" "67" "68" "Fizz" "Buzz"
## [71] "71" "Fizz" "73" "74" "FizzBuzz" "76" "77" "Fizz" "79" "Buzz"
## [81] "Fizz" "82" "83" "Fizz" "Buzz" "86" "Fizz" "88" "89" "FizzBuzz"
## [91] "91" "92" "Fizz" "94" "Buzz" "Fizz" "97" "98" "Fizz" "Buzz"


参考資料など