Почему в Scala тип коллекции не возвращает Iterable (как в .Net)? - PullRequest
6 голосов
/ 31 мая 2011

В Scala вы можете сделать

val l = List(1, 2, 3)
l.filter(_ > 2)               // returns a List[Int]
val s = Set("hello", "world")
s.map(_.length)               // returns a Set[Int]

Вопрос в том, почему это полезно?

Коллекции Scala, вероятно, являются единственной существующей структурой коллекций, которая делает это. Сообщество Scala, похоже, согласилось с тем, что эта функциональность необходима. Тем не менее, никто не пропускает эту функциональность на других языках. Пример C # (изменение имени в соответствии с именем Scala):

var l = new List<int> { 1, 2, 3 }
l.filter(i => i > 2)          // always returns Iterable[Int]
l.filter(i => i > 2).toList   // if I want a List, no problem
l.filter(i => i > 2).toSet    // or I want a Set

В .NET я всегда получаю Iterable, и мне решать, что с ним делать. (Это также делает коллекции .NET очень простыми).

Пример Scala с множеством заставляет меня сделать набор длин из набора строк. Но что, если я просто хочу перебрать длины, или создать список длин, или оставить Iterable, чтобы отфильтровать его позже. Создание набора сразу кажется бессмысленным. ( EDIT : collection.view предоставляет более простую функциональность .NET, хорошо)

Я уверен, что вы покажете мне примеры, когда .NET-подход абсолютно неверен или снижает производительность, но я просто не вижу его (использую .NET годами).

Ответы [ 4 ]

10 голосов
/ 31 мая 2011

Не полный ответ на ваш вопрос, но Scala никогда не заставляет использовать один тип коллекции над другим.Вы можете написать код, подобный следующему:

import collection._
import immutable._

val s = Set("hello", "world")
val l: Vector[Int] = s.map(_.length)(breakOut)

Подробнее о breakOut в Подробный ответ Даниэля Собрала на другой вопрос.

Если вы хотите, чтобы вашmap или filter для ленивой оценки, используйте это:

s.view.map(_.length)

Все это поведение позволяет легко интегрировать ваши новые классы коллекции и наследовать все мощные возможности стандартной коллекции без дублирования кодавсе это гарантирует, что YourSpecialCollection#filter вернет экземпляр YourSpecialCollection;YourSpecialCollection#map возвращает экземпляр YourSpecialCollection, если он поддерживает сопоставляемый тип, или встроенную резервную коллекцию, если это не так (например, то, что происходит с вами, вызывает map для BitSet).Конечно, итератор C # не имеет .toMySpecialCollection метода.

См. Также: «Интеграция новых наборов и карт» в Архитектура коллекций Scala .

9 голосов
/ 31 мая 2011

Scala следует «принципу унифицированного типа возвращаемого значения», гарантируя, что вы всегда получите соответствующий тип возвращаемого значения, а не теряете эту информацию, как в C #.

Причина, по которой это делает C #, заключается в том, что их система типов не достаточно хороша, чтобы предоставить эти гарантии, не переопределяя всю реализацию каждого метода в каждом подклассе. Scala решает эту проблему с использованием Высших Родственных Типов.

Почему Scala имеет единственную коллекционную среду, которая делает это? Потому что это сложнее, чем думает большинство людей, особенно когда нужно интегрировать такие вещи, как Strings и Arrays, которые не являются «настоящими» коллекциями:

// This stays a String:
scala> "Foobar".map(identity)
res27: String = Foobar
// But this falls back to the "nearest" appropriate type:
scala> "Foobar".map(_.toInt)
res29: scala.collection.immutable.IndexedSeq[Int] = Vector(70, 111, 111, 98, 97, 114)
7 голосов
/ 31 мая 2011

Если у вас есть Set, и операция над ним возвращает Iterable, в то время как его тип времени выполнения все еще равен Set, то вы теряете важную информацию о его поведении и доступе к конкретному наборуметоды.

Кстати: - это другие языки, ведущие себя одинаково, например, Haskell, который сильно повлиял на Scala.Версия Haskell map будет выглядеть так в переводе на Scala (без implicit magic):

//the functor type class
trait Functor[C[_]] {
   def fmap[A,B](f: A => B, coll: C[A]) : C[B]
}

//an instance  
object ListFunctor extends Functor[List] {
   def fmap[A,B](f: A => B, list: List[A]) : List[B] = list.map(f)
}

//usage 
val list = ListFunctor.fmap((x:Int) => x*x, List(1,2,3))

И я думаю, что сообщество Haskell также ценит эту функцию: -)

4 голосов
/ 31 мая 2011

Это вопрос последовательности.Вещи, какие они есть, и вернуть такие вещи, как ониВы можете зависеть от этого.

Разница, которую вы здесь делаете, заключается в строгости.Строгий метод оценивается немедленно, в то время как не строгий метод оценивается только по мере необходимости.Это имеет последствия.Возьмите этот простой пример:

def print5(it: Iterable[Int]) = {
    var flag = true
    it.filter(_ => flag).foreach { i =>
        flag = i < 5
        println(i)
    }
}

Протестируйте его с этими двумя коллекциями:

print5(List.range(1, 10))
print5(Stream.range(1, 10))

Здесь List строгий, поэтому его методы строгие.И наоборот, Stream не является строгим, поэтому его методы не являются строгими.

Так что это вообще не имеет отношения к Iterable - в конце концов, оба List и Stream являются Iterable.Изменение типа возвращаемого значения коллекции может вызвать все виды проблем - по крайней мере, это усложнит задачу сохранения постоянной структуры данных.

С другой стороны, существуют преимущества задержки некоторых операций,даже на строгой коллекции.Вот несколько способов сделать это:

// Get an iterator explicitly, if it's going to be used only once
def print5I(it: Iterable[Int]) = {
    var flag = true
    it.iterator.filter(_ => flag).foreach { i =>
        flag = i < 5
        println(i)
    }
}

// Get a Stream explicitly, if the result will be reused
def print5S(it: Iterable[Int]) = {
    var flag = true
    it.toStream.filter(_ => flag).foreach { i =>
        flag = i < 5
        println(i)
    }
}

// Use a view, which provides non-strictness for some methods
def print5V(it: Iterable[Int]) = {
    var flag = true
    it.view.filter(_ => flag).foreach { i =>
        flag = i < 5
        println(i)
    }
}

// Use withFilter, which is explicitly designed to be used as a non-strict filter
def print5W(it: Iterable[Int]) = {
    var flag = true
    it.withFilter(_ => flag).foreach { i =>
        flag = i < 5
        println(i)
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...