(это основано на статье в http://bertails.org/2015/02/15/abstract-algebraic-data-type)Как контролировать распознанный тип для связанных переменных в сопоставлении с образцом в присутствии высших-kinded типов
Во-первых, я определяю абстрактную версию scala.Option
.
import scala.language.higherKinds
trait OptionSig {
type Option[+_]
type Some[+A] <: Option[A]
type None <: Option[Nothing]
}
abstract class OptionOps[Sig <: OptionSig] extends Extractors[Sig] {
def some[A](x: A): Sig#Some[A]
def none: Sig#None
def fold[A, B](opt: Sig#Option[A])(ifNone: => B, ifSome: A => B): B
}
Я хочу, чтобы иметь возможность использовать сопоставление с образцом на Sig#Option[A]
так Extractors
выглядит следующим образом:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](opt: Sig#Option[A]): scala.Option[A] =
fold(opt)(scala.None, a => scala.Some(a))
}
object None {
def unapply[A](opt: Sig#Option[A]): Option[Unit] =
fold(opt)(scala.Some(()), _ => scala.None)
}
}
Теперь я могу написать эту программу:
class Program[Sig <: OptionSig](implicit ops: OptionOps[Sig]) extends App {
import ops._
val opt: Sig#Option[Int] = some(42)
opt match {
case None(_) => sys.error("")
case Some(42) => println("yay")
case Some(_) => sys.error("")
}
}
И я могу проверить его с этой реализацией.
trait ScalaOption extends OptionSig {
type Option[+A] = scala.Option[A]
type Some[+A] = scala.Some[A]
type None = scala.None.type
}
object ScalaOption {
implicit object ops extends OptionOps[ScalaOption] {
def some[A](x: A): ScalaOption#Some[A] = scala.Some(x)
val none: ScalaOption#None = scala.None
def fold[A, B](opt: ScalaOption#Option[A])(ifNone: => B, ifSome: A => B): B =
opt match {
case scala.None => ifNone
case scala.Some(x) => ifSome(x)
}
}
}
object Main extends Program[ScalaOption]
Похоже, что это работает, но есть одна досадная вещь, которую я не могу понять.
С, scala.Option
, тип s
в Option(42) match { case s @ Some(42) => s }
- Some[Int]
. Но с моим фрагментом выше, это Sig#Option[Int]
, и я хотел бы сделать это Sig#Some[Int]
.
Так что я попытался следующие, чтобы быть ближе к тому, что scalac создает для своих конкретных классов:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](s: Sig#Some[A]): scala.Option[A] =
fold(s)(scala.None, a => scala.Some(a))
}
object None {
def unapply(n: Sig#None): Option[Unit] =
fold(n)(scala.Some(()), (_: Any) => scala.None)
}
}
Но теперь я получаю предупреждения, как следующее:
[warn] Main.scala:78: abstract type pattern Sig#None is unchecked since it is eliminated by erasure
[warn] case None(_) => sys.error("")
Я не знаю, почему это происходит как Sig#None
является подтипом Sig#Option[Int]
, и это известно во время компиляции.
Также время выполнения все еще в порядке, но предполагаемый тип все еще не тот, которого я ожидал.
Так вопросы
- почему тип стирание упоминается здесь, несмотря на информацию подтипирования?
- как получить
Sig#Option[Int]
дляs
в(some(42): Sig#Option[Int]) match { case s @ Some(42) => s }
Отличный ответ. Вы знаете простой способ увидеть, как скомпилировано сопоставление шаблонов? Или какие-либо биты документации/спецификации, объясняющие, как она компилируется? – betehess
@betehess Вы можете использовать: javap в REPL (не работает в 2.10). Вы можете увидеть пример сессии здесь (см. Строку 103 для instanceof): https://gist.github.com/tixxit/cacceaff6b25bafb2816 – tixxit