最近型クラスとかを勉強しているのだけれど、その中でconst
関数というのを見て感心した。
const
Haskellだと以下の通りに書く:
const x _ = x
Scala風に書く(ここからはScalaで書いていく)と以下の通りに書く:
def const[A, B](x: A) = (_: B) => x
つまり、2つ(curryingされた)引数を取るのだけれど、最初に受け取ったほうを返して、2つ目は捨ててしまう。
const(1)(2) // => 1
これだけだと、はあそうですかという感じ。待って!まだタブを閉じないで!
折り畳み関数に入れる
const
の何が面白いかって、fold
やreduce
系の折り畳み系関数に入れると怪奇現象が起こるところ。const
は2引数関数なので、fold
やreduce
に入れることができるんですね。ちょっとやってみましょう。
Haskellと違ってcurryingした形のままでは使えないので、Function.uncurried
を使う必要がある。
val lis = List("あ", "を", "に", "よ", "し") lis.reduce(Function.uncurried(const)) // => "あ"
reduce
関数にconst
を入れたら、最初の要素が返ってきた!なぜかというと、
// constを◊とおいて中置記法で書くと…… あ ◊ を ◊ に ◊ よ ◊ し = ((あ ◊ を) ◊ (に ◊ よ)) ◊ し = (あ ◊ に) ◊ し = あ ◊ し = あ
となるため。reduceLeft
でもreduceRight
でも結局最初の要素が返ってくる。const
は結合律を満たすもんね。
(X ◊ Y) ◊ Z = X ◊ Z = X X ◊ (Y ◊ Z) = X ◊ Y = X
const
にidentity
を渡すと、さらに面白いことが起こる(Scala 3でしかコンパイルしない)。
val cident = const(identity)
lis.reduce(Function.uncurried(cident)) // => "し"
なんと最後の要素が返される。なんだそれ。計算過程をたどってみると分かる。
const(identity) = _ => identity = _ => x => x
今度は2つ目の引数を返す関数になった。おもしろい。
おまけ
ちょっと実験だ。試しにidentity
を二回渡してみよう。
val cident2 = const(identity)(identity) cident2(1) // => 1
うーん。2引数関数ではなくなってしまったので、ちょっと思っていたのと違う結果になった。
constに1引数関数を入れると、2つ目の引数だけ使う2引数関数にできる
identity
のほかにも、const
に関数を与えると、さらに奇妙な現象が起こる。
val lis = List("あ", "を", "に", "よ", "し") val inc = (x: Int) => x + 1 println(lis.foldRight(0)(Function.uncurried(const(inc))) // 5
リストの長さを返した。すごい。これは、単純にInt
をインクリメントする関数を、const
を使って2引数化したことで、リストの内容を完全に無視して長さを計算している。
const
にinc
を渡すと、最初の引数を返すので、const(inc)(x)(y)
はinc(y)
と等しくなる。
const
は最初の引数を返す関数なので、それに引き摺られてしまいそうになるが、const
に1引数関数を渡すと、最初の引数を無視して2つ目の引数が渡されるように変換される。まずconst
が2つ目の引数を捨ててくれるのだけれど、カリー化しているので、実際に使われるときは1つ目の引数が捨てられているように見えるというトリックになっている。だから右から畳まれるfoldRight
を使う必要があったのか!
Const
const
関数をデータ構造の形にしたConst
というものを考えることもできて、catsなどに導入されている。
case class Const[X, Y](getConst: X)
型Y
はまったくのダミーで、型合わせにしか使わない。
Const
はFunctor
やApplicative
のインスタンスにできるから、const
関数のより一般化された形としてデータ構造の変換などに用いられる。
- 与えられた関数
A => B
を全く無視して型だけConst[X, A]
からConst[X, B]
にするようなfmap
を与えれば、Const
をFunctor
にできる。 X
がMonoid
なら、その実装を使ってConst[X, *]
をApplicative
にできる。Monoid
のempty
を使うことでpure
を実装できる。Monoid
のcombine
を使うことでap
を実装できる。
うれし~。何がうれしいかというと、foldMap
を勝手に導出できたりする。
クリスマスプレゼントはこれで決まりだね!!