Подтвердить что ты не робот

Каковы классы типов в Scala полезными для?

Как я понимаю из этого сообщения в блоге "типы классов" в Scala - это всего лишь "шаблон", реализованный с чертами и неявными адаптерами.

Как говорится в блоге, если у меня есть черта A и адаптер B -> A, тогда я могу вызвать функцию, для которой требуется аргумент типа A, с аргументом типа B без явного вызова этого адаптера.

Я нашел это приятным, но не особенно полезным. Не могли бы вы привести пример использования/пример, который показывает, для чего эта функция полезна?

4b9b3361

Ответ 1

Один вариант использования, по запросу...

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

Один из способов сделать это - иметь некоторый признак Addable, который должен быть унаследован каждым отдельным типом, который может быть добавлен вместе, или неявное преобразование в Addable, если оно связано с объектами из сторонней библиотеки, которые вы не можете модифицировать интерфейсы.

Этот подход становится быстро подавляющим, когда вы также хотите начать добавлять другие такие операции, которые можно выполнить для списка объектов. Это также плохо работает, если вам нужны альтернативы (например, добавление двух осциллограмм объединяет их или накладывает их?) Решение представляет собой специальный полиморфизм, в котором вы можете выбрать и выбрать поведение, которое будет модифицировано для существующих типов.

Для исходной задачи вы можете реализовать класс типа Addable:

trait Addable[T] {
  def zero: T
  def append(a: T, b: T): T
}
//yup, it our friend the monoid, with a different name!

Затем вы можете создавать неявные подклассы, соответствующие каждому типу, который вы хотите добавить:

implicit object IntIsAddable extends Addable[Int] {
  def zero = 0
  def append(a: Int, b: Int) = a + b
}

implicit object StringIsAddable extends Addable[String] {
  def zero = ""
  def append(a: String, b: String) = a + b
}

//etc...

Метод суммирования списка затем становится тривиальным для записи...

def sum[T](xs: List[T])(implicit addable: Addable[T]) =
  xs.FoldLeft(addable.zero)(addable.append)

//or the same thing, using context bounds:

def sum[T : Addable](xs: List[T]) = {
  val addable = implicitly[Addable[T]]
  xs.FoldLeft(addable.zero)(addable.append)
}

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

Кстати, это как раз подход, применяемый API-интерфейсом 2.8. Хотя метод sum определен в TraversableLike вместо List, а класс типа Numeric (он также содержит несколько операций, кроме как только zero и append)

Ответ 2

Перечитайте там первый комментарий:

Важное различие между типами классов и интерфейсами состоит в том, что для класса А, который должен быть "членом" интерфейса, он должен объявить так на сайте своего собственного определения. Напротив, любой тип может быть добавлен в класс типа в любое время, если вы можете предоставить необходимые определения, и поэтому члены класса типа в любой момент времени зависят от текущей области. Поэтому нам все равно, если создатель A предвидел тип класса, к которому мы хотим принадлежать; если нет, мы можем просто создать собственное определение, показывающее, что оно действительно принадлежит, а затем использовать его соответствующим образом. Таким образом, это не только лучшее решение, чем адаптеры, в некотором смысле оно устраняет все проблемы, к которым предназначались адаптеры.

Я думаю, что это самое важное преимущество классов типов.

Кроме того, они правильно обрабатывают случаи, когда операции не имеют аргумента типа, который мы отправляем, или имеют более одного. Например. рассмотрим класс этого типа:

case class Default[T](val default: T)

object Default {
  implicit def IntDefault: Default[Int] = Default(0)

  implicit def OptionDefault[T]: Default[Option[T]] = Default(None)

  ...
}

Ответ 3

Я думаю о типах классов как о способности добавлять безопасные метаданные типа в класс.

Итак, сначала вы определяете класс для моделирования проблемной области, а затем задумываетесь о добавлении метаданных. Такие вещи, как Equals, Hashable, Viewable и т.д. Это создает разделение проблемной области и механики на использование класса и открывает подклассу, потому что класс является более компактным.

Кроме этого, вы можете добавлять классы типов в любом месте области, а не только там, где определено класс, и вы можете изменять реализации. Например, если я вычисляю хэш-код для класса Point с помощью Point # hashCode, тогда я ограничен этой конкретной реализацией, которая может не создавать хорошего распределения значений для определенного набора точек, которые у меня есть. Но если я использую Hashable [Point], тогда я могу предоставить свою собственную реализацию.

[Обновлено с помощью примера] В качестве примера можно привести пример использования на прошлой неделе. В нашем продукте есть несколько случаев, когда Карты содержат контейнеры в качестве значений. Например, Map[Int, List[String]] или Map[String, Set[Int]]. Добавление в эти коллекции может быть многословным:

map += key -> (value :: map.getOrElse(key, List()))

Итак, я хотел иметь функцию, которая обертывает это, чтобы я мог написать

map +++= key -> value

Основная проблема заключается в том, что коллекции не все имеют одинаковые методы для добавления элементов. У некоторых есть "+", а другие:+. Я также хотел сохранить эффективность добавления элементов в список, поэтому я не хотел использовать fold/map, которые создают новые коллекции.

Решение состоит в использовании классов типов:

  trait Addable[C, CC] {
    def add(c: C, cc: CC) : CC
    def empty: CC
  }

  object Addable {
    implicit def listAddable[A] = new Addable[A, List[A]] {
      def empty = Nil

      def add(c: A, cc: List[A]) = c :: cc
    }

    implicit def addableAddable[A, Add](implicit cbf: CanBuildFrom[Add, A, Add]) = new Addable[A, Add] {
      def empty = cbf().result

      def add(c: A, cc: Add) = (cbf(cc) += c).result
    }
  }

Здесь я определил класс типа Addable, который может добавить элемент C в коллекцию CC. У меня есть 2 реализации по умолчанию: для списков, использующих :: и для других коллекций, используя каркас строителя.

Затем, используя этот тип, класс:

class RichCollectionMap[A, C, B[_], M[X, Y] <: collection.Map[X, Y]](map: M[A, B[C]])(implicit adder: Addable[C, B[C]]) {
    def updateSeq[That](a: A, c: C)(implicit cbf: CanBuildFrom[M[A, B[C]], (A, B[C]), That]): That  = {
      val pair = (a -> adder.add(c, map.getOrElse(a, adder.empty) ))
      (map + pair).asInstanceOf[That]
    }

    def +++[That](t: (A, C))(implicit cbf: CanBuildFrom[M[A, B[C]], (A, B[C]), That]): That  = updateSeq(t._1, t._2)(cbf)
  }

  implicit def toRichCollectionMap[A, C, B[_], M[X, Y] <: col

Специальный бит использует adder.add для добавления элементов и adder.empty для создания новых коллекций для новых ключей.

Для сравнения, без классов типов у меня было бы 3 варианта: 1. написать метод для каждого типа коллекции. Например, addElementToSubList и addElementToSet и т.д. Это создает много шаблонов в реализации и загрязняет пространство имен 2. использовать рефлексию, чтобы определить, является ли подпапка списком/множеством. Это сложно, поскольку карта пуста для начала (конечно, scala помогает здесь также с Manifests) 3. иметь класс типа "бедный человек", требуя, чтобы пользователь поставил сумматор. Так что что-то вроде addToMap(map, key, value, adder), которое является простым уродливым

Ответ 4

Еще один способ, которым я нахожу это сообщение в блоге, - это то, где он описывает классы: Монады - это не метафоры

Поиск статьи для typeclass. Это должен быть первый матч. В этой статье автор предоставляет пример класса Monad.

Ответ 5

Один из способов взглянуть на классы типов - это включить ретроактивное расширение или ретроактивный полиморфизм. Есть несколько отличных сообщений Случайные чудеса и Даниэль Вестхайд, которые показывают примеры использования классов типов в Scala для достижения этого.

Здесь сообщение в моем блоге  который исследует различные методы в Scala ретроактивного супертипания, своего рода ретроактивное расширение, включая пример типа typeclass.

Ответ 6

Тема форума " Что делает классы классов лучше, чем черты?" делает несколько интересных моментов:

  • Typeclasses может очень легко представлять представления, которые довольно сложно представить в присутствии подтипов, таких как равенство и упорядочение.
    Упражнение: создайте небольшую иерархию классов/признаков и попытайтесь реализовать .equals для каждого класса/признака таким образом, чтобы операция над произвольными экземплярами из иерархии была правильно рефлексивной, симметричной и транзитивной.
  • Typeclasses позволяют вам представить доказательства того, что тип, находящийся за пределами вашего "контроля", соответствует некоторому поведению.
    Кто-то еще может быть членом вашего класса.
  • Вы не можете выразить "этот метод принимает/возвращает значение того же типа, что и приемник метода" в терминах подтипирования, но это (очень полезное) ограничение простое с помощью классов типов. Это проблема f-bounded types (где F-ограниченный тип параметризуется по своим подтипам).
  • Все операции, определенные в признаке, требуют экземпляра; всегда есть аргумент this. Таким образом, вы не можете определить, например, метод fromString(s:String): Foo на trait Foo таким образом, что вы можете вызвать его без экземпляра Foo.
    В Scala это проявляется в том, что люди отчаянно пытаются абстрагироваться от сопутствующих объектов.
    Но это просто с классом типа, как показано нулевым элементом в этом примере моноида.
  • Typeclasses можно определить индуктивно; например, если у вас есть JsonCodec[Woozle], вы можете получить JsonCodec[List[Woozle]] бесплатно.
    Пример выше иллюстрирует это для "вещей, которые вы можете добавить вместе".

Ответ 7

Я не знаю другого случая использования, кроме Ad-hoc polyorhism, который объясняется здесь возможно.

Ответ 8

Оба implicits и typeclasses используются для Тип-преобразования. Основным прецедентом для них обоих является предоставление ad-hoc-полиморфизма (i.e) для классов, которые вы не можете изменить, но ожидайте тип наследования полиморфизма. В случае implicits вы можете использовать неявный def или неявный класс (который является вашим классом-оболочкой, но скрытым от клиента). Typeclasses более мощные, поскольку они могут добавить функциональность к уже существующей цепочке наследования (например: Ordering [T] в функции сортировки scala). Для более подробной информации вы можете увидеть https://lakshmirajagopalan.github.io/diving-into-scala-typeclasses/

Ответ 9

В классах классов scala

  • Включает ad-hoc-полиморфизм
  • Статически типизированный (т.е. безопасный тип)
  • Заимствован из Haskell
  • Решает проблему с выражением

Поведение может быть расширено  - во время компиляции  - после факта  - без изменения/перекомпиляции существующего кода

Scala Implicits

Последний список параметров метода может быть помечен как неявный

  • Неявные параметры заполняются компилятором

  • По сути, вам нужны доказательства компилятора

  • ... например, существование класса типа в области

  • Вы также можете указать параметры явно, если необходимо

Ниже пример расширения в классе String с реализацией класса класса расширяет класс с помощью новых методов, хотя строка является окончательной:)

/**
* Created by nihat.hosgur on 2/19/17.
*/
case class PrintTwiceString(val original: String) {
   def printTwice = original + original
}

object TypeClassString extends App {
  implicit def stringToString(s: String) = PrintTwiceString(s)
  val name: String = "Nihat"
  name.printTwice
}

Ответ 10

Это - важное различие (необходимое для функционального программирования):

введите описание изображения здесь

рассмотрим inc:Num a=> a -> a:

a получено то же самое, что возвращается, это невозможно сделать с подтипированием