Question

Supposons que je veux créer un trait dans lequel je peux mélanger dans n'importe quel [t] traversable. En fin de compte, je veux pouvoir dire des choses comme:

val m = Map("name" -> "foo") with MoreFilterOperations

et avoir des méthodes sur des opérations plus ultra qui sont exprimées dans tout ce qui est traversable a à offrir, comme:

def filterFirstTwo(f: (T) => Boolean) = filter(f) take 2

Cependant, le problème est clairement que T n'est pas défini comme un paramètre de type sur des opérations plus élevées. Une fois que je fais cela, c'est faisable bien sûr, mais mon code se lirait:

val m = Map("name" -> "foo") with MoreFilterOperations[(String,String)]

ou si je définis une variable de ce type:

var m2: Map[String,String] with MoreFilterOperations[(String,String)] = ...

lequel est façon de verbeux à mon goût. Je voudrais que le trait soit défini de telle manière que je pourrais écrire ce dernier comme:

var m2: Map[String,String] with MoreFilterOperations

J'ai essayé les types de soi, les membres de type abstrait, mais cela n'a rien fait d'utile. Des indices?

Était-ce utile?

La solution

Map("name" -> "foo") est une invocation de fonction et non un constructeur, cela signifie que vous ne pouvez pas écrire:

Map("name" -> "foo") with MoreFilterOperations

Plus que vous pouvez écrire

val m = Map("name" -> "foo")
val m2 = m with MoreFilterOperations

Pour obtenir un mixin, vous devez utiliser un type de béton, une première tentative naïve serait quelque chose comme ceci:

def EnhMap[K,V](entries: (K,V)*) =
  new collection.immutable.HashMap[K,V] with MoreFilterOptions[(K,V)] ++ entries

Utilisation d'une méthode d'usine ici pour éviter d'avoir à dupliquer les paramètres de type. Cependant, cela ne fonctionnera pas, car le ++ la méthode va juste retourner un vieux vieux HashMap, sans le mixin!

La solution (comme Sam l'a suggéré) consiste à utiliser une conversion implicite pour ajouter la méthode PIMPED. Cela vous permettra de transformer la carte avec toutes les techniques habituelles et de pouvoir utiliser vos méthodes supplémentaires sur la carte résultante. Je le ferais normalement avec une classe au lieu d'un trait, car avoir des paramètres de constructeur disponibles conduit à une syntaxe plus propre:

class MoreFilterOperations[T](t: Traversable[T]) {
  def filterFirstTwo(f: (T) => Boolean) = t filter f take 2
}

object MoreFilterOperations {
  implicit def traversableToFilterOps[T](t:Traversable[T]) =
    new MoreFilterOperations(t)
}

Cela vous permet de écrire alors

val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3")
val m2 = m filterFirstTwo (_._1.startsWith("n"))

Mais il ne joue toujours pas bien avec le cadre des collections. Vous avez commencé avec une carte et vous avez fini avec un Traversable. Ce n'est pas ainsi que les choses sont censées fonctionner. L'astuce ici consiste également à résumer sur le type de collection en utilisant des types de kilomètres plus élevés

import collection.TraversableLike

class MoreFilterOperations[Repr <% TraversableLike[T,Repr], T] (xs: Repr) {
  def filterFirstTwo(f: (T) => Boolean) = xs filter f take 2
}

Assez simple. Vous devez fournir Repr, le type représentant la collection, et T, le type d'éléments. j'utilise TraversableLike à la place de Traversable car il intègre sa représentation; sans cela, filterFirstTwo retournerait un Traversable Quel que soit le type de départ.

Maintenant les conversions implicites. C'est là que les choses deviennent un peu plus difficiles dans la notation de type. Tout d'abord, j'utilise un type de kilomètre plus élevé pour capturer la représentation de la collection: CC[X] <: Traversable[X], ces paramètres le CC type, qui doit être une sous-classe de traversée (notez l'utilisation de X En tant qu'espace réservé ici, CC[_] <: Traversable[_] ne signifie pas la même chose).

Il y a aussi un implicite CC[T] <:< TraversableLike[T,CC[T]], que le compilateur utilise pour garantir statiquement que notre collection CC[T] est vraiment une sous-classe de TraversableLike Et donc un argument valable pour le MoreFilterOperations constructeur:

object MoreFilterOperations {
  implicit def traversableToFilterOps[CC[X] <: Traversable[X], T]
  (xs: CC[T])(implicit witness: CC[T] <:< TraversableLike[T,CC[T]]) =
    new MoreFilterOperations[CC[T], T](xs)
}

Jusqu'ici tout va bien. Mais il y a encore un problème ... cela ne fonctionnera pas avec les cartes, car ils prennent deux paramètres de type. La solution consiste à ajouter un autre implicite au MoreFilterOperations objet, en utilisant les mêmes principes qu'avant:

implicit def mapToFilterOps[CC[KX,VX] <: Map[KX,VX], K, V]
(xs: CC[K,V])(implicit witness: CC[K,V] <:< TraversableLike[(K,V),CC[K,V]]) =
  new MoreFilterOperations[CC[K,V],(K,V)](xs)

La vraie beauté arrive lorsque vous voulez également travailler avec des types qui ne sont pas réellement des collections, mais peuvent être considérés comme s'ils étaient. Se souvenir du Repr <% TraversableLike dans le MoreFilterOperations constructeur? C'est une vue limitée, et permet aux types qui peuvent être implicitement convertis en TraversableLike ainsi que des sous-classes directes. Les cordes en sont un exemple classique:

implicit def stringToFilterOps
(xs: String)(implicit witness: String <%< TraversableLike[Char,String])
: MoreFilterOperations[String, Char] =
  new MoreFilterOperations[String, Char](xs)

Si vous l'exécutez maintenant sur le REP:

val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3")
//  m: scala.collection.immutable.Map[java.lang.String,java.lang.String] =
//    Map((name,foo), (name2,foo2), (name3,foo3))

val m2 = m filterFirstTwo (_._1.startsWith("n"))
//  m2: scala.collection.immutable.Map[java.lang.String,java.lang.String] =
//    Map((name,foo), (name2,foo2))

"qaxfwcyebovjnbointofm" filterFirstTwo (_ < 'g')
//res5: String = af

La carte entre, la carte sort. La chaîne entre, String sort. etc...

Je ne l'ai pas essayé avec un Stream pourtant, ou un Set, ou un Vector, mais vous pouvez être convaincu que si vous le faisiez, il rendrait le même type de collection avec lequel vous avez commencé.

Autres conseils

Ce n'est pas tout à fait ce que vous avez demandé, mais vous pouvez résoudre ce problème avec des implicits:

trait MoreFilterOperations[T] {
  def filterFirstTwo(f: (T) => Boolean) = traversable.filter(f) take 2
  def traversable:Traversable[T]
}

object FilterImplicits {
  implicit def traversableToFilterOps[T](t:Traversable[T]) = new MoreFilterOperations[T] { val traversable = t }
}

object test {

  import FilterImplicits._

  val m = Map("name" -> "foo", "name2" -> "foo2", "name3" -> "foo3")
  val r = m.filterFirstTwo(_._1.startsWith("n"))
}

scala> test.r
res2: Traversable[(java.lang.String, java.lang.String)] = Map((name,foo), (name2,foo2))

La bibliothèque standard de Scala utilise des implicits à cet effet. Par exemple "123".toInt. Je pense que c'est la meilleure façon dans ce cas.

Sinon, vous devrez passer par la mise en œuvre complète de votre "carte avec des opérations supplémentaires", car les collections immuables nécessitent création de nouvelles instances de votre nouvelle classe mixte.

Avec des collections mutables, vous pourriez faire quelque chose comme ceci:

object FooBar {
  trait MoreFilterOperations[T] {
    this: Traversable[T] =>
    def filterFirstTwo(f: (T) => Boolean) = filter(f) take 2
  }

  object moreFilterOperations {
    def ~:[K, V](m: Map[K, V]) = new collection.mutable.HashMap[K, V] with MoreFilterOperations[(K, V)] {
      this ++= m
    }
  }

  def main(args: Array[String]) {
    val m = Map("a" -> 1, "b" -> 2, "c" -> 3) ~: moreFilterOperations
    println(m.filterFirstTwo(_ => true))
  }
}

Je préfère utiliser des implicits.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top