[Scala] SAM型とラムダ構文

2018/02/15   #Scala 
このエントリーをはてなブックマークに追加

背景

このような Semigroup のトレイトがあり、

trait Semigroup[A] {

  def append(a1: A, a2: => A): A

}

こういうことができる。

scala> val x: Semigroup[String] = (a1, a2) => a1.toUpperCase + a2.toUpperCase
x: Semigroup[String] = $$Lambda$1229/1427479449@77724c7d

突然のラムダ式で Semigroup[String] を生成している。
Java でいう Functional Interface 的な感じ?かと思ったがよくわからなかったので調べてみた。

SAM type (Single Abstract Method type)

Scala のラムダ式は trait に自動変換できる - Qiita

abstract method が一つだけ定義された trait は、ラムダ式から変換できるらしい。 @xuwei_k 先生曰く、2.12 から入った機能とのこと。 SAM conversion と呼ばれている。

なるほど、たしかに上記の Semigroup は、抽象メソッドを 1つだけもつ trait である。
試しに抽象メソッドを複数にしたらコンパイラに怒られた。

trait Semigroup[A] {

  def append(a1: A, a2: => A): A

  def hoge(a1: A, a2: => Int): A  //2つ目の抽象メソッド

}

先程のラムダ式のところで型を指定しろと怒られる。

scala> val x: Semigroup[String] = (a1, a2) => a1.toUpperCase + a2.toUpperCase
<console>:14: error: missing parameter type
       val x: Semigroup[String] = (a1, a2) => a1.toUpperCase + a2.toUpperCase
                                   ^
<console>:14: error: missing parameter type
       val x: Semigroup[String] = (a1, a2) => a1.toUpperCase + a2.toUpperCase
                                       ^

で、型を指定しても怒られる。
なぜなら、複数の抽象メソッド → Java でいう Functional Interface じゃない → SAM 型じゃなくて普通の FunctionN になる。からだと思う。

scala> val x: Semigroup[String] = (a1: String, a2: String) => a1.toUpperCase + a2.toUpperCase
<console>:14: error: type mismatch;
 found   : (String, String) => String
 required: til.sam_conversion.Semigroup[String]
       val x: Semigroup[String] = (a1: String, a2: String) => a1.toUpperCase + a2.toUpperCase

抽象メソッドじゃない普通のメソッドは追加しても大丈夫。

trait Semigroup[A] {

  def append(a1: A, a2: => A): A

  def hoge(a1: A, a2: => Int): A = a1 //a1をそのまま返す普通のメソッド

}

Scala 2.12.0 リリースノート | eed3si9n

トレイトはインターフェイスにコンパイルされる

SAM型に対するラムダ構文