Обеспечить разницу типов - PullRequest
54 голосов
/ 02 августа 2011

В Scala я могу обеспечить равенство типов во время компиляции.Например:

case class Foo[A,B]( a: A, b: B )( implicit ev: A =:= B )

scala> Foo( 1, 2 )
res3: Foo[Int,Int] = Foo(1,2)

scala> Foo( 1, "2" )
<console>:10: error: Cannot prove that Int =:= java.lang.String.

Есть ли способ обеспечить, чтобы тип A и тип B отличались?

Ответы [ 7 ]

44 голосов
/ 04 августа 2011

У меня есть более простое решение, которое также использует неоднозначность,

trait =!=[A, B]

implicit def neq[A, B] : A =!= B = null

// This pair excludes the A =:= B case
implicit def neqAmbig1[A] : A =!= A = null
implicit def neqAmbig2[A] : A =!= A = null

Исходный вариант использования,

case class Foo[A,B](a : A, b : B)(implicit ev: A =!= B)
new Foo(1, "1")
new Foo("foo", Some("foo"))

// These don't compile
// new Foo(1, 1)
// new Foo("foo", "foo")
// new Foo(Some("foo"), Some("foo"))

Обновление

Мы можем связать это с моими «магическими приемами системы типов» (спасибо @jpp ;-) следующим образом,

type ¬[T] = T => Nothing
implicit def neg[T, U](t : T)(implicit ev : T =!= U) : ¬[U] = null

def notString[T <% ¬[String]](t : T) = t

Пример сеанса REPL,

scala> val ns1 = notString(1)
ns1: Int = 1

scala> val ns2 = notString(1.0)
ns2: Double = 1.0

scala> val ns3 = notString(Some("foo"))
ns3: Some[java.lang.String] = Some(foo)

scala> val ns4 = notString("foo")
<console>:14: error: No implicit view available from 
  java.lang.String => (String) => Nothing.
       val ns4 = notString2("foo")
                            ^
24 голосов
/ 03 августа 2011

Отбрасывая идеи Жан-Филиппа, это работает:

sealed class =!=[A,B]

trait LowerPriorityImplicits {
  implicit def equal[A]: =!=[A, A] = sys.error("should not be called")
}
object =!= extends LowerPriorityImplicits {
  implicit def nequal[A,B](implicit same: A =:= B = null): =!=[A,B] = 
    if (same != null) sys.error("should not be called explicitly with same type")
    else new =!=[A,B]
}     

case class Foo[A,B](a: A, b: B)(implicit e: A =!= B)

Тогда:

// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))

// doesn't compile
// Foo(1f, 1f)
// Foo("", "")

Я бы, вероятно, упростил это следующим образом, поскольку проверки на "обман" всегда можно обойти (например, Foo(1, 1)(null) или =!=.nequal(null)):

sealed class =!=[A,B]

trait LowerPriorityImplicits {
  /** do not call explicitly! */
  implicit def equal[A]: =!=[A, A] = sys.error("should not be called")
}
object =!= extends LowerPriorityImplicits {
  /** do not call explicitly! */
  implicit def nequal[A,B]: =!=[A,B] = new =!=[A,B]
}
15 голосов
/ 11 июня 2013

Мне понравилась простота и эффективность первого решения Майлза Сабина, но он был немного недоволен тем, что ошибка, которую мы получаем, не очень помогает:

Примером со следующим определением:

def f[T]( implicit e: T =!= String ) {}

Попытка выполнить f[String] не удастся скомпилировать с:

<console>:10: error: ambiguous implicit values:
 both method neqAmbig1 in object =!= of type [A]=> =!=[A,A]
 and method neqAmbig2 in object =!= of type [A]=> =!=[A,A]
 match expected type =!=[String,String]
              f[String]
               ^

Я бы предпочел, чтобы компилятор сказал мне что-то вроде "T не отличается от String"получается, что это довольно просто, если добавить еще один уровень имплицитов таким образом, чтобы мы превратили ошибку неоднозначность в ошибку неявная не найдена .С этого момента мы можем использовать аннотацию implicitNotFound для создания пользовательского сообщения об ошибке:

@annotation.implicitNotFound(msg = "Cannot prove that ${A} =!= ${B}.")
trait =!=[A,B]
object =!= {
  class Impl[A, B]
  object Impl {
    implicit def neq[A, B] : A Impl B = null
    implicit def neqAmbig1[A] : A Impl A = null
    implicit def neqAmbig2[A] : A Impl A = null
  }

  implicit def foo[A,B]( implicit e: A Impl B ): A =!= B = null
}

Теперь давайте попробуем позвонить f[String]:

scala> f[String]
<console>:10: error: Cannot prove that String =!= String.
              f[String]
           ^

Это лучше.Спасибо компилятору.

В качестве последнего трюка для тех, кто любит синтаксический сахар, связанный с контекстом, можно определить этот псевдоним (на основе типа лямбда-выражений):

type IsNot[A] = { type λ[B] = A =!= B }

Тогда мы можем определить f вот так:

def f[T:IsNot[String]#λ] {}

То, что легче читать, очень субъективно.В любом случае это определенно короче, чем написание полного списка неявных параметров.

ОБНОВЛЕНИЕ : для полноты здесь эквивалентный код для выражения, что A не является подтипом B:

@annotation.implicitNotFound(msg = "Cannot prove that ${A} <:!< ${B}.")
trait <:!<[A,B]
object <:!< {
  class Impl[A, B]
  object Impl {
    implicit def nsub[A, B] : A Impl B = null
    implicit def nsubAmbig1[A, B>:A] : A Impl B = null
    implicit def nsubAmbig2[A, B>:A] : A Impl B = null
  }

  implicit def foo[A,B]( implicit e: A Impl B ): A <:!< B = null
}

type IsNotSub[B] = { type λ[A] = A <:!< B }

И для выражения того, что A не конвертируется в B:

@annotation.implicitNotFound(msg = "Cannot prove that ${A} <%!< ${B}.")
trait <%!<[A,B]
object <%!< {
  class Impl[A, B]
  object Impl {
    implicit def nconv[A, B] : A Impl B = null
    implicit def nconvAmbig1[A<%B, B] : A Impl B = null
    implicit def nconvAmbig2[A<%B, B] : A Impl B = null
  }

  implicit def foo[A,B]( implicit e: A Impl B ): A <%!< B = null
}

type IsNotView[B] = { type λ[A] = A <%!< B }
9 голосов
/ 02 августа 2011

Основываясь на идее Landei , кажется, что работает следующее:

case class Foo[A, B <: A, C <: A]( a: B, b: C)(implicit f: AnyVal <:< A)

scala> Foo(1f, 1.0)
res75: Foo[AnyVal,Float,Double] = Foo(1.0,1.0)

scala> Foo("", 1.0)
res76: Foo[Any,java.lang.String,Double] = Foo(,1.0)

scala> Foo(1f, 1f)
<console>:10: error: Cannot prove that AnyVal <:< Float.
       Foo(1f, 1f)
          ^

scala> Foo("", "")
<console>:10: error: Cannot prove that AnyVal <:< java.lang.String.
       Foo("", "")
          ^

scala> Foo("", 1)
res79: Foo[Any,java.lang.String,Int] = Foo(,1)
6 голосов
/ 03 августа 2011

Вот еще одна попытка:

class =!=[A, B] private () extends NotNull

object =!= {
  implicit def notMeantToBeCalled1[A, B >: A, C >: B <: A]: =!=[B, A] = error("should not be called")
  implicit def notMeantToBeCalled2[A, B >: A, C >: B <: A]: =!=[B, A] = error("should not be called")
  implicit def unambigouslyDifferent[A, B](implicit same: A =:= B = null): =!=[A, B] =
    if (same != null) error("should not be called explicitly with the same type")
    else new =!=
}

case class Foo[A, B](a: A, b: B)(implicit ev: A =!= B)

Затем снова:

// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))

// doesn't compile
// Foo(1f, 1f)
// Foo("", "")

Как и в моем другом предложении, цель здесь состоит в том, чтобы ввести двусмысленность во время компиляции, когда A и B совпадают. Здесь мы приводим два следствия для случая, когда A совпадает с B, и однозначное неявное, когда это не так.

Обратите внимание, что проблема в том, что вы все равно можете явно указать неявный параметр, вручную вызвав =!=.notMeantToBeCalled1 или =!=.unambigouslyDifferent. Я не мог придумать, как это предотвратить во время компиляции. Однако мы можем выдать исключение во время выполнения, используя хитрость, согласно которой unambigouslyDifferent требует самого параметра подтверждения, указывающего, совпадает ли A с B. Но подождите ... Разве мы не пытаемся доказать обратное? Да, и поэтому этот неявный параметр same имеет значение по умолчанию null. И мы ожидаем, что он будет null для всех законных применений - единственный раз, когда он не будет null, это когда злой пользователь звонит, например. Foo(1f, 1f)(=:=.unambiguouslyDifferent[Float, Float]), и там мы можем предотвратить этот обман, создав исключение.

1 голос
/ 03 августа 2011

Это не ответ, просто начало того, что я мог бы считать ответом. Приведенный ниже код вернет либо Yes, либо No в зависимости от того, являются ли типы одинаковыми или нет, если вы запросите implicitly[AreEqual[A,B]] Как оттуда перейти к проверке, которую я так и не смог выяснить. Может быть, весь подход обречен, может, кто-то может что-то из этого сделать. Имейте в виду, implicitly[No[A, B]] будет всегда возвращать что-то, это нельзя использовать. : - (

class AreEqual[A, B]
trait LowerPriorityImplicits {
  implicit def toNo[A : Manifest, B : Manifest]: No[A, B] = No[A, B]
}
object AreEqual extends LowerPriorityImplicits {
  implicit def toYes[A, B](implicit ev: A =:= B, m1: Manifest[A], m2: Manifest[B]): Yes[A, B] = Yes[A, B]
}

case class Yes[A : Manifest, B : Manifest]() extends AreEqual[A, B] {
  override def toString: String = "Yes(%s, %s)" format (manifest[A].toString, manifest[B].toString)
}
case class No[A : Manifest, B : Manifest]() extends AreEqual[A, B] {
  override def toString: String = "No(%s, %s)" format (manifest[A].toString, manifest[B].toString)
}

Тест:

scala> implicitly[AreEqual[String, Option[String]]]
res0: AreEqual[String,Option[String]] = No(java.lang.String, scala.Option[java.lang.String])

scala> implicitly[AreEqual[String, String]]
res1: AreEqual[String,String] = Yes(java.lang.String, java.lang.String)
1 голос
/ 03 августа 2011

Как насчет чего-то подобного?

class Foo[A, B] private (a: A, b: B)

object Foo {
  def apply[A, B <: A, C >: A <: B](a: A, b: B)(implicit nothing: Nothing) = nothing
  def apply[A, B >: A, C >: B <: A](a: A, b: B)(implicit nothing: Nothing, dummy: DummyImplicit) = nothing
  def apply[A, B](a: A, b: B): Foo[A, B] = new Foo(a, b)
}

Тогда:

// compiles:
Foo(1f, 1.0)
Foo("", 1.0)
Foo("", 1)
Foo("Fish", Some("Fish"))

// doesn't compile
// Foo(1f, 1f)
// Foo("", "")

Идея состоит в том, чтобы сделать разрешение неоднозначным, когда A совпадает с B, и однозначным, когда они не совпадают. Чтобы еще раз подчеркнуть, что неоднозначные методы не должны вызываться, я добавил неявный тип Nothing, который никогда не должен присутствовать (и, безусловно, должен выглядеть неправильно для вызывающей стороны, если они пытаются вставить один явно). (Роль DummyImplicit состоит только в том, чтобы дать различную подпись первым двум методам.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...