Применить разницу в типах
в 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 ответов:
рифф от идей Жан-Филиппа, это работает:
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] }
у меня есть более простое решение, которое также использует двусмысленность,
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") ^
мне понравилась простота и эффективность первого решения Майлза Сабина, но был немного недоволен тем, что ошибка, которую мы получаем, не очень полезна:
на примере со следующим определением:
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 не отличается от строки" Оказывается, это довольно легко, если добавить еще один уровень из неявные преобразования таким образом, что мы превращаем неопределенность ошибка в подразумевается не нашел ошибка. С этого момента мы можем использовать
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
is не является подтипом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 }
на основе 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)
вот еще одна попытка:
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
is когда неприятный пользователь вызывает, напримерFoo(1f, 1f)(=:=.unambiguouslyDifferent[Float, Float])
, и тут мы можем предотвратить этот обман, выдавая исключение.
как насчет чего-то вроде этого, тогда?
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
- это просто дать различная сигнатура для первых двух методов.)
это не ответ, просто начало того, что я мог придумать ответ. Приведенный ниже код вернет либо
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) }