Как сформировать объединение scala SortedMaps? - PullRequest
6 голосов
/ 21 декабря 2009

(Я использую ночные Scala и вижу то же поведение в 2.8.0b1 RC4. Я новичок в Scala.)

У меня есть два SortedMap, из которых я хотел бы создать союз. Вот код, который я хотел бы использовать:

import scala.collection._

object ViewBoundExample {
    class X
    def combine[Y](a: SortedMap[X, Y], b: SortedMap[X, Y]): SortedMap[X, Y] = {
        a ++ b
    }
    implicit def orderedX(x: X): Ordered[X] = new Ordered[X] { def compare(that: X) = 0 }
}

Идея здесь в том, что «неявное» утверждение означает, что X s можно преобразовать в Ordered[X] s, и тогда имеет смысл объединить SortedMap s в другой SortedMap, а не просто в карту.

Когда я компилирую, я получаю

sieversii:scala-2.8.0.Beta1-RC4 scott$ bin/scalac -versionScala compiler version
2.8.0.Beta1-RC4 -- Copyright 2002-2010, LAMP/EPFL

sieversii:scala-2.8.0.Beta1-RC4 scott$ bin/scalac ViewBoundExample.scala
ViewBoundExample.scala:8: error: type arguments [ViewBoundExample.X] do not
    conform to method ordered's type parameter bounds [A <: scala.math.Ordered[A]]
        a ++ b
          ^
one error found

Кажется, моя проблема исчезла бы, если бы граница параметра этого типа была [A <% scala.math.Ordered[A]], а не [A <: scala.math.Ordered[A]]. К сожалению, я даже не могу понять, где живет «заказанный» метод! Может кто-нибудь помочь мне отследить это?

Если это не удастся, что я собираюсь сделать, чтобы создать объединение двух SortedMap с? Если я удаляю тип возвращаемого комбайна (или меняю его на Map), все работает нормально - но тогда я не могу рассчитывать на сортировку возврата!

Ответы [ 2 ]

5 голосов
/ 21 декабря 2009

В настоящее время вы используете черту scala.collection.SortedMap, чей метод ++ унаследован от черты MapLike. Следовательно, вы видите следующее поведение:

scala> import scala.collection.SortedMap
import scala.collection.SortedMap

scala> val a = SortedMap(1->2, 3->4)
a: scala.collection.SortedMap[Int,Int] = Map(1 -> 2, 3 -> 4)

scala> val b = SortedMap(2->3, 4->5)
b: scala.collection.SortedMap[Int,Int] = Map(2 -> 3, 4 -> 5)

scala> a ++ b
res0: scala.collection.Map[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5)

scala> b ++ a
res1: scala.collection.Map[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5)

Тип возвращаемого результата ++ - Map[Int, Int], потому что это будет единственный тип, который имеет смысл для метода ++ объекта MapLike для возврата. Похоже, что ++ сохраняет отсортированное свойство SortedMap, что, я полагаю, объясняется тем, что ++ использует абстрактные методы для конкатенации, и эти абстрактные методы определены так, чтобы поддерживать порядок карты.

Чтобы объединить две отсортированные карты, я предлагаю вам использовать scala.collection.immutable.SortedMap.

scala> import scala.collection.immutable.SortedMap
import scala.collection.immutable.SortedMap

scala> val a = SortedMap(1->2, 3->4)
a: scala.collection.immutable.SortedMap[Int,Int] = Map(1 -> 2, 3 -> 4)

scala> val b = SortedMap(2->3, 4->5)
b: scala.collection.immutable.SortedMap[Int,Int] = Map(2 -> 3, 4 -> 5)

scala> a ++ b
res2: scala.collection.immutable.SortedMap[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5)

scala> b ++ a
res3: scala.collection.immutable.SortedMap[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5)

Эта реализация черты SortedMap объявляет метод ++, который возвращает SortedMap.

Теперь пара ответов на ваши вопросы о границах типов:

  • Ordered[T] - это черта, которая при смешивании в классе указывает, что этот класс можно сравнивать, используя <, >, =, >=, <=. Вам просто нужно определить абстрактный метод compare(that: T), который возвращает -1 для this < that, 1 для this > that и 0 для this == that. Затем все остальные методы реализуются в признаке на основе результата compare.

  • T <% U представляет представление, ограниченное в Scala. Это означает, что тип T является подтипом U или может быть неявно преобразован в U путем неявного преобразования в области видимости. Код работает, если вы поставите <%, но не с <:, так как X не является подтипом Ordered[X], но может быть неявно преобразовано в Ordered[X] с помощью неявного преобразования OrderedX.

Редактировать: По поводу вашего комментария. Если вы используете scala.collection.immutable.SortedMap, вы все равно программируете для интерфейса, а не для реализации, так как неизменяемый SortedMap определяется как trait. Вы можете рассматривать его как более специализированную черту scala.collection.SortedMap, которая обеспечивает дополнительные операции (например, ++, которая возвращает SortedMap) и свойство быть неизменным. Это соответствует философии Scala - предпочтение неизменности - поэтому я не вижу никаких проблем с использованием неизменяемого SortedMap. В этом случае вы можете гарантировать, что результат будет определенно отсортирован, и это нельзя изменить, так как коллекция является неизменной.

Хотя я все еще нахожу странным, что scala.collection.SortedMap не предоставляет метод ++, который в результате возвращает SortedMap. Все ограниченное тестирование, которое я провел, похоже, предполагает, что результат объединения двух scala.collection.SortedMap действительно дает карту, которая сохраняет отсортированное свойство.

4 голосов
/ 21 декабря 2009

Вы выбрали крепкий орешек для новичка в Scala! : -)

Хорошо, краткий тур, не ожидайте, что полностью поймете это прямо сейчас. Во-первых, обратите внимание, что проблема возникает по методу ++. В поисках его определения мы находим его по признаку MapLike, получая либо Iterator, либо Traversable. Поскольку y является SortedMap, то используется версия Traversable.

Обратите внимание, что в расширенной сигнатуре типа передается CanBuildFrom. Он передается неявно, поэтому вам не нужно беспокоиться об этом. Однако, чтобы понять, что происходит, на этот раз вы делаете.

Вы можете найти CanBuildFrom, щелкнув по нему там, где он указан в определении ++, или с помощью фильтрации. Как упомянул Рэндалл в комментариях, в левом верхнем углу скалярной страницы есть не отмеченное пустое поле. Вам просто нужно нажать там и ввести текст, и он вернет совпадения для того, что вы ввели.

Итак, найдите черту CanBuildFrom в ScalaDoc и выберите ее. Он имеет большое количество подклассов, каждый из которых отвечает за создание определенного типа коллекции. Найдите и выберите подкласс SortedMapCanBuildFrom. Это класс объекта, который вам нужен для получения SortedMap из Traversable. Обратите внимание на конструктор экземпляра (конструктор для класса), который получает неявный параметр Ordering. Теперь мы приближаемся.

На этот раз используйте фильтр фильтра для поиска Ordering. В его сопутствующем объекте (нажмите на маленькое «o» имя) размещается неявное выражение, которое будет генерировать Ordering s, так как сопутствующие объекты проверяются на наличие последствий, генерирующих экземпляры или преобразования для этого класса. Внутри признака LowPriorityOrderingImplicits определяется, какой объект Ordering расширяется, и, глядя на него, вы увидите метод ordered[A <: Ordered[A]], который будет производить Ordering, необходимый ... или будет производить его, если только там не было проблемой.

Можно предположить, что неявного преобразования из X в Ordered[X] будет достаточно, так же, как я это делал до того, как более внимательно изучить это. Это, однако, является преобразованием объектов , и ordered ожидает получения типа , который является подтипом Ordered[X]. Хотя можно преобразовать объект типа X в объект типа Ordered[X], X сам по себе не является подтипом Ordered[X], поэтому его нельзя передать в качестве параметра в ordered.

С другой стороны, вы можете создать неявное val Ordering[X] вместо def Ordered[X], и вы справитесь с проблемой. В частности:

object ViewBoundExample {
    class X
    def combine[Y](a: SortedMap[X, Y], b: SortedMap[X, Y]): SortedMap[X, Y] = {
        a ++ b
    }
    implicit val orderingX = new Ordering[X] { def compare(x: X, y: X) = 0 }
}

Я думаю, что у большинства людей первоначальная реакция на Ordered / Ordering должна быть одной из недоумений: почему занятия для одной и той же вещи? Первый расширяет java.lang.Comparable, а второй расширяет java.util.Comparator. Увы, подпись типа для compare в основном суммирует основное различие:

def compare(that: A): Int     // Ordered
def compare(x: T, y: T): Int  // Ordering

Использование Ordered[A] требует либо A для расширения Ordered[A], что потребует от него изменения определения A или передачи метода который может конвертировать A в Ordered[A]. Scala вполне может выполнить последнее легко, но тогда у вас есть для преобразования каждого экземпляра перед сравнением.

С другой стороны, использование Ordering[A] требует создания одного объекта, как показано выше. Когда вы используете его, вы просто передаете два объекта типа A в compare - никакие объекты не создаются в процессе.

Таким образом, есть некоторый прирост производительности, но есть гораздо более важная причина для предпочтения Scala Ordering, а не Ordered. Посмотрите еще раз на объект-компаньон Ordering. Вы заметите, что для многих классов Scala, определенных там, есть несколько последствий. Вы можете вспомнить, как я упоминал ранее, что неявный для класса T будет искать внутри объекта-компаньона T, и это именно то, что происходит.

Это можно сделать и для Ordered. Однако, и это является камнем преткновения, это означает, что любой метод, поддерживающий как Ordering, так и Ordered, потерпит неудачу! Это потому, что Scala будет искать неявное, чтобы заставить его работать, и найдет два: один для Ordering, один для Ordered. Будучи не в состоянии решить, что именно вы хотели, Scala отказывается с сообщением об ошибке. Итак, выбор должен был быть сделан, и у Ordering было больше возможностей для этого.

Дух, я забыл объяснить, почему подпись не определяется как ordered[A <% Ordered[A]] вместо ordered[A <: Ordered[A]]. Я подозреваю, что из-за этого возникнет ошибка с двойной импликацией, о которой я упоминал ранее, но я спрошу парня, который действительно сделал эту вещь и имел двойные неявные проблемы, проблематичен ли этот конкретный метод.

...