Общее понимание в Scala - PullRequest
4 голосов
/ 17 июля 2011

Насколько я понимаю, нотация для понимания в Scala опирается на первый генератор, чтобы определить, как элементы должны быть объединены.А именно, for (i <- list) yield i возвращает список, а for (i <- set) yield i возвращает набор.

Мне было интересно, есть ли способ указать, как элементы комбинируются независимо от свойств первого генератора.Например, я хотел бы получить "набор всех элементов из данного списка" или "сумму всех элементов из данного набора" .Единственный способ, который я нашел, - это сначала создать список или набор, как предписано нотацией для понимания, а затем применить к нему функцию преобразования - создать бесполезную структуру данных в процессе.

Что у меня естьимеется в виду общая «алгебраическая» запись понимания, как она существует, например, в Ateji PX:

`+ { i | int i : set }               // the sum of all elements from a given set
set() { i | int i : list }           // the set of all elements from a given list
concat(",") { s | String s : list }  // string concatenation with a separator symbol

Здесь первый элемент (`+, set(), concat(",")) является так называемым«моноид», который определяет, как элементы комбинируются, независимо от структуры первого генератора (может быть несколько генераторов и фильтров, я просто старался, чтобы примеры были краткими). ​​

Любая идея о том, как достичьпохожий результат в Scala, сохраняя при этом красивую и лаконичную запись?Насколько я понимаю, нотация для понимания понятна в компиляторе и не может быть обновлена.

Спасибо за ваш отзыв.

Ответы [ 4 ]

12 голосов
/ 17 июля 2011

О для понимания

Понимание for в scala является синтаксическим сахаром для вызовов flatMap, filter, map и foreach. Точно так же, как вызовы этих методов, тип целевой коллекции приводит к типу возвращаемой коллекции. То есть:

list map f   //is a List
vector map f // is a Vector

Это свойство является одной из базовых целей проектирования библиотеки коллекций scala и может рассматриваться как желательное в большинстве ситуаций.

Ответ на вопрос

Вам не нужно создавать какие-либо промежуточные коллекции, конечно:

(list.view map (_.prop)).toSet //uses list.view

(list.iterator map (_.prop)).toSet //uses iterator

(for { l <- list.view} yield l.prop).toSet //uses view

(Set.empty[Prop] /: coll) { _ + _.prop } //uses foldLeft

Все будут давать наборы без создания ненужных коллекций. Мое личное предпочтение - первое. С точки зрения идиоматического манипулирования коллекцией scala каждая «коллекция» поставляется с этими методами:

//Conversions
toSeq
toSet
toArray
toList
toIndexedSeq
iterator
toStream

//Strings
mkString

//accumulation
sum 

Последний используется, когда тип элемента коллекции имеет неявный экземпляр Numeric в области видимости; такие как:

Set(1, 2, 3, 4).sum //10
Set('a, 'b).sum //does not compile

Обратите внимание, что пример конкатенации строк в Scala выглядит следующим образом:

list.mkString(",")

А в библиотеке scalaz FP может выглядеть что-то вроде (которая использует Monoid для суммирования строк):

list.intercalate(",").asMA.sum

Ваши предложения не похожи на Scala; Я не уверен, вдохновлены ли они другим языком.

4 голосов
/ 17 июля 2011

foldLeft? Это то, что вы описываете.

Сумма всех элементов из данного набора:

(0 /: Set(1,2,3))(_ + _)

набор всех элементов из данного списка

(Set[Int]() /: List(1,2,3,2,1))((acc,x) => acc + x)

Конкатенация строк с символом разделителя:

("" /: List("a", "b"))(_ + _) // (edit - ok concat a bit more verbose:
("" /: List("a", "b"))((acc,x) => acc + (if (acc == "") "" else ",")  + x)
1 голос
/ 18 июля 2011

Вы также можете принудительно задать тип результата для понимания, явно указав неявный параметр CanBuildFrom как scala.collection.breakout и указав тип результата.

Рассмотрим этот сеанс REPL:

scala> val list = List(1, 1, 2, 2, 3, 3)
list: List[Int] = List(1, 1, 2, 2, 3, 3)

scala> val res = for(i <- list) yield i
res: List[Int] = List(1, 1, 2, 2, 3, 3)

scala> val res: Set[Int] = (for(i <- list) yield i)(collection.breakOut)
res: Set[Int] = Set(1, 2, 3)

Это приводит к ошибке типа, если не указывать CanBuildFrom явно:

scala> val res: Set[Int] = for(i <- list) yield i
<console>:8: error: type mismatch;
 found   : List[Int]
 required: Set[Int]
       val res: Set[Int] = for(i <- list) yield i
                                 ^

Для более глубокого понимания этого я предлагаю следующее чтение:

http://www.scala -lang.org / доку / файлы / сборники-апи / коллекции-impl.html

0 голосов
/ 18 июля 2011

Если вы хотите использовать для понимания и все еще быть в состоянии объединить ваши значения в какое-то значение результата, вы можете сделать следующее.

case class WithCollector[B, A](init: B)(p: (B, A) => B) {
  var x: B = init
  val collect = { (y: A) => { x = p(x, y) } }
  def apply(pr: (A => Unit) => Unit) = {
    pr(collect)
    x
  }
}

// Some examples
object Test {

  def main(args: Array[String]): Unit = {

    // It's still functional
    val r1 = WithCollector[Int, Int](0)(_ + _) { collect =>
      for (i <- 1 to 10; if i % 2 == 0; j <- 1 to 3) collect(i + j)
    }

    println(r1) // 120

    import collection.mutable.Set

    val r2 = WithCollector[Set[Int], Int](Set[Int]())(_ += _) { collect =>
      for (i <- 1 to 10; if i % 2 == 0; j <- 1 to 3) collect(i + j)
    }

    println(r2) // Set(9, 10, 11, 6, 13, 4, 12, 3, 7, 8, 5)
  }

}
...