2

(это основано на статье в 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 }

ответ

4

К сожалению, вы не можете делать то, что вы хотите. Проблема в том, что недостаточно знать, что скалак Sig#None <: Sig#Option[A], он должен быть в состоянии проверить, что значение, которое оно передает до unapply, фактически составляет Sig#None. Это работает для scala.Option, потому что компилятор может сгенерировать проверку instanceof, чтобы убедиться, что тип фактически является Some или None, прежде чем передать его методу unapply. Если он не прошел проверку, то пропускает, что шаблон (и unapply никогда не вызывается).

В вашем случае, поскольку scalac только знает, что opt является Sig#Option[Int], он ничего не может сделать, чтобы гарантировать, что значение фактически Some или None перед передачей его вместе с unapply.

И что он делает? Он передает стоимость в любом случае! Что это значит? Ну, давайте изменим ваши экстракторы немного:

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] = 
     scala.Some(()) 
    } 
} 

Все, что мы сделали, перестали использовать fold в None случае. Поскольку мы знаем, аргумент должен быть Sig#None, зачем даже звонить по телефону fold, правильно? Я имею в виду, мы не ожидали, что здесь будет передан Sig#Some, верно?

Когда мы запустим этот пример, вы попали в RuntimeException, потому что наш первый матч шаблон успешно и вызывает ???. В случае scala.Option шаблон будет терпеть неудачу, потому что он охраняется сгенерированным instanceof чек.

Я gisted еще один пример, который показывает другую опасность, где мы добавим небольшое ограничение на Sig#Some, которое позволит нам избежать fold в Some случае тоже: https://gist.github.com/tixxit/ab99b741d3f5d2668b91

В любом случае, ваш конкретный случай является технически безопасным. Мы знаем, что вы использовали fold, поэтому его можно использовать с Sig#Option. Проблема в том, что scalac этого не знает.

+0

Отличный ответ. Вы знаете простой способ увидеть, как скомпилировано сопоставление шаблонов? Или какие-либо биты документации/спецификации, объясняющие, как она компилируется? – betehess

+1

@betehess Вы можете использовать: javap в REPL (не работает в 2.10). Вы можете увидеть пример сессии здесь (см. Строку 103 для instanceof): https://gist.github.com/tixxit/cacceaff6b25bafb2816 – tixxit

Смежные вопросы