Scala универсальная функция путаницы - PullRequest
3 голосов
/ 14 февраля 2012

У меня следующая ситуация:

Получил метод def f(lst: List[Any]), который выполняет некоторое преобразование списка и возвращает результат этого преобразования (все эти Any являются классами case). Что мне нужно сделать, это когда список ввода пуст, сгенерировать список, содержащий один элемент правильного типа, и выполнить это преобразование с ним.

Можно ли гарантировать на уровне типов, что у некоторого класса case есть конструктор без аргументов? Если да, то чем заменить Any? Если нет, как лучше всего это сделать? Может быть, я должен просто изменить свой метод на что-то вроде def f[T](lst: List[T], default: T)?

Любая помощь приветствуется.

Ответы [ 3 ]

5 голосов
/ 14 февраля 2012

Вы ищете что-то подобное?

import scalaz._
import Scalaz._

scala> def f[A : Zero](lst: List[A]) = {
     |   val xs = if(lst.isEmpty) List(mzero[A]) else lst
     |   xs ++ xs // some transformation
     | }
f: [A](lst: List[A])(implicit evidence$1: scalaz.Zero[A])List[A]

scala> f(List.empty[Int])
res1: List[Int] = List(0, 0)

scala> f(List("hello", "world"))
res2: List[java.lang.String] = List(hello, world, hello, world)

Если да, вы можете сослаться на этот пост Я писал по этому вопросу некоторое время назад.

2 голосов
/ 15 февраля 2012

Простой ответ - нет, система типов не может сказать вам, есть ли у класса конструктор по умолчанию. Помните, что у классов case обычно нет конструктора по умолчанию, так как классы case без аргументов устарели Концепция конструкторов по умолчанию не очень полезна для неизменных объектов. AFAIK, нет никаких причин, почему не должно быть в принципе (Scala поддерживает структурные типы, где у типа должен быть метод с определенным именем), но это потребовало бы изменения языка. Вы можете проверить во время выполнения с отражением, но это не то, что вы хотите.

Однако вы можете использовать шаблон класса типов, чтобы значение по умолчанию находилось в области видимости. Концептуально это очень похоже на добавление дополнительного аргумента по умолчанию, как предлагается в ОП, но с использованием имплицитов, чтобы скрыть их. Он активно используется в библиотеке коллекций. Ответ об отсутствующем факторе с использованием scalaz.Zero является особым случаем этого, но его очень легко сделать в ванильном Scala и для некоторого произвольного значения по умолчанию, которое не обязательно является каким-то нулем.

case class Default[T](default: T)

case class Foo(value: String)
case class Bar(value: Int)

implicit val fooDefault = Default(Foo("I'm a default Foo"))  // note 1

Теперь давайте рассмотрим пример использования:

def firstItem[T](lst: List[T]) (implicit ev: Default[T]) =   // note 2
  if (lst.isEmpty) ev.default else lst.head

val fooList      = List(Foo("cogito"), Foo("ergo"), Foo("sum"))
val emptyFooList = List[Foo]()
val barList      = List(Bar(101), Bar(102))
val emptyBarList = List[Bar]()

firstItem(fooList)                        // Foo("cogito")
firstItem(emptyFooList)                   // Foo("I'm a default Foo")
firstItem(barList)                        // ** error: missing implicit **

Итак, мы видим, что это компилируется с List[Foo], но List[Bar] не принимается, поскольку не существует неявного Default[Bar] (примечание 3).


примечание 1: Это неявное значение может быть определено в object Foo - что позволит убедиться, что оно находится в области видимости, если вы импортируете класс в другом месте. Но это не обязательно: вы также можете определить аналогичные значения для произвольного класса, Int, String, что угодно (попробуйте).

примечание 2: Это соответствует версии с сахаром def firstItem[T: Default](lst: List[T]) = ..., где вы вызываете ev с implicitly[Default[T]]. Выбирай.

примечание 3: мы можем заставить его работать, просто поставив один:

firstItem(barList)(Default(Bar(42)))      // Bar(101)
firstItem(emptyBarList)(Default(Bar(42))) // Bar(42)
1 голос
/ 14 февраля 2012

Я не совсем уверен, что вы пытаетесь сделать (возможно, вы можете включить несколько дополнительных деталей), но несколько советов, которые я могу дать сразу же, если у вас есть куча классов связанных дел, они все должны расширить Запечатанная черта. Это не только позволит вам повысить безопасность типов (не более Any), но и компилятор сможет проверять полное совпадение шаблонов. Например:

sealed trait Foo
case class Bar(x: Int) extends Foo
case class Baz(y: String) extends Foo

Тогда вы можете определить свою функцию следующим образом:

def f[T <: Foo](lst: List[Foo], default: T)//...

Это позволит list содержать элементы любого из классов case, но потребует, чтобы default был типом, заданным параметром type (который должен быть подтипом Foo)

...