Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

const関数おもしろい(そしてアプリカティブファンクタへ……)

最近型クラスとかを勉強しているのだけれど、その中でconst関数というのを見て感心した。

yomi322.hateblo.jp

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引数化したことで、リストの内容を完全に無視して長さを計算している。

f:id:Windymelt:20211128064801p:plain
関数の流れ

constにincを渡すと、最初の引数を返すので、const(inc)(x)(y)はinc(y)と等しくなる。

f:id:Windymelt:20211128065814p:plain

constは最初の引数を返す関数なので、それに引き摺られてしまいそうになるが、constに1引数関数を渡すと、最初の引数を無視して2つ目の引数が渡されるように変換される。まずconstが2つ目の引数を捨ててくれるのだけれど、カリー化しているので、実際に使われるときは1つ目の引数が捨てられているように見えるというトリックになっている。だから右から畳まれるfoldRightを使う必要があったのか!

Const

const関数をデータ構造の形にしたConstというものを考えることもできて、catsなどに導入されている。

case class Const[X, Y](getConst: X)

型Yはまったくのダミーで、型合わせにしか使わない。

eed3si9n.com

typelevel.org

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を勝手に導出できたりする。

クリスマスプレゼントはこれで決まりだね!!

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?