57 votes

Différence de type d'application

En Scala, je peux faire respecter l'égalité des types au moment de la compilation. Par exemple :

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.

Existe-t-il un moyen d'imposer que le type A et le type B soient différents ?

51voto

Miles Sabin Points 13604

J'ai une solution plus simple, qui tire également parti de l'ambiguïté,

 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
 

Le cas d'utilisation d'origine,

 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"))
 

Mettre à jour

Nous pouvons associer cela à mes "astuces de typesystem magiques" (merci @jpp ;-) comme suit,

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

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

Exemple de session 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")
                            ^
 

25voto

Aaron Novstrup Points 10742

En reprenant les idées de Jean-Philippe, cela fonctionne :

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)

Ensuite :

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

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

Je simplifierais probablement cela comme suit, puisque les contrôles de "tricherie" peuvent toujours être contournés de toute façon (par ex. Foo(1, 1)(null) ou =!=.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]
}

16voto

Régis Jean-Gilles Points 14463

J'ai aimé la simplicité et l'efficacité de la première solution de Miles Sabin, mais j'ai été un peu insatisfait du fait que l'erreur que nous obtenons n'est pas très utile :

Par exemple avec la définition suivante :

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

Essayer de faire f[String] ne pourra pas être compilé avec :

<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]
               ^

Je préférerais que le compilateur me dise quelque chose du genre "T n'est pas différent de String". Il s'avère qu'il est assez facile d'ajouter encore un autre niveau d'implicites de telle sorte que l'on transforme l'expression "T" en "String". ambiguïté erreur en une implicite non trouvé erreur. A partir de là, nous pouvons utiliser le implicitNotFound pour émettre un message d'erreur personnalisé :

@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
}

Essayons maintenant d'appeler f[String] :

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

C'est mieux. Merci au compilateur.

Comme dernière astuce pour ceux qui aiment le sucre syntaxique lié au contexte, on peut définir cet alias (basé sur les lambdas de type) :

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

Nous pouvons alors définir f comme ça :

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

La question de savoir s'il est plus facile à lire est hautement subjective. Dans tous les cas, c'est certainement plus court que d'écrire la liste complète des paramètres implicites.

UPDATE : Pour être complet, voici le code équivalent pour exprimer que A n'est pas un sous-type de 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 }

Et pour avoir exprimé que A n'est pas convertible en 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 }

9voto

Vasil Remeniuk Points 12487

Sur la base de Landei La méthode suivante semble fonctionner :

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)

6voto

Jean-Philippe Pellet Points 25240

Voici une autre tentative:

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)

Puis, à nouveau:

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

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

Comme dans mon autre proposition, le but ici est de présenter une compilation toute ambiguïté lors de la A et B sont les mêmes. Ici, nous vous fournissons deux implicites pour le cas où l' A est le même que B, et sans ambiguïté implicite lorsque ce n'est pas le cas.

Notez que le problème, c'est qu'on peut encore fournir explicitement le paramètre implicite manuellement en appelant =!=.notMeantToBeCalled1 ou =!=.unambigouslyDifferent. Je ne pouvais pas penser à une façon d'éviter cela au moment de la compilation. Cependant, on peut lancer une exception lors de l'exécution, avec le truc qu' unambigouslyDifferent nécessite un paramètre de preuve lui-même, en indiquant si A est le même que B. Mais attendez... ne sommes-nous pas essayer de prouver le contraire? Oui, et c'est pourquoi qu' same implicite paramètre a une valeur par défaut de null. Et nous nous attendons à être null pour tous les usages - la seule fois où il ne serait pas null , c'est quand un méchant de l'utilisateur appels par exemple, Foo(1f, 1f)(=:=.unambiguouslyDifferent[Float, Float]), et de là, nous pouvons empêcher cette tricherie par la levée d'une exception.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X