DSL: ярлык для применения функции к каждому элементу коллекции определенного типа c - PullRequest
0 голосов
/ 29 апреля 2020

Я новичок в scala и пытаюсь использовать его большие возможности для создания простого DSL. Я обнаружил, что при использовании DSL можно создать что-то похожее на приведенный ниже пример, и мне было интересно, как это было возможно:

val oranges = Array.tabulate(2)(i => Orange(i+10)) //Orange(diameter)
// Oranges of diameter 10 and 11
oranges incDiameter 5
// Oranges in the array now have diameter 15 and 16

Что я не понимаю, так это как мы можем напрямую применить функцию incDiameter к массив, так как мы не можем добавить функцию incDiameter к классу Array scala; Насколько я понимаю, это эквивалентно выполнению oranges.incDiameter (5), например, Array [Fruit] .incDiameter (5), но поскольку incDiameter не объявлен в классе Array, где он должен быть объявлен для того, чтобы 3-я строка Работа ? Моя интуиция заключается в том, что, вероятно, есть способ изменить способ применения функций к итерируемым элементам наших собственных классов, поэтому третья строка примера фактически преобразуется в oranges.map (_. IncDiameter (5)), но я нигде не видел этого

1 Ответ

1 голос
/ 29 апреля 2020

Методы расширения:

implicit class OrangesOps(val oranges: Array[Orange]) extends AnyVal {

  def incDiameter(by: Int): Array[Orange] = oranges.map(_.incDiameter(5))
}

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

trait IncreasableDiameter[A] {

  def incDiamater(what: A)(by: Int): A
}

implicit class DiamatersOps[A](val what: A) extends AnyVal {

  def incDiameter(by: Int)(implicit increasable: IncreasableDiameter[A]): A =
    increasable.incDiamater(what)(by)
}

тогда , если вы можете предоставить неявное доказательство того, что для вашего типа существует экземпляр класса типа, вы сможете использовать метод incDiameter (при условии, что как методы экземпляра, так и расширения будут определены / импортированы в область действия)

implicit val orangesIncreasable: IncreasableDiameter[Orange] =
  new IncreasableDiameter[Orange] {
    def incDiamater(what: Orange)(by: Int): Orange = what.incDiamater(by)
  }

implicit def arrayIncreasable[A](
  implicit increasable: IncreasableDiameter[A]
): IncreasableDiameter[Array[A]] = new IncreasableDiameter[Array[A]] {

  def incDiamater(what: Array[A])(by: Int): Array[A] = what.map(_.incDiamater(by))
}

Это позволит вам вызывать эту операцию для:

val orange: Orange = ...
orange.incDiameter(5) // oranges built-in method
Array(orange).incDiameter(5) // no build in method, but extension method can be used
                             // because we can produce type class for Array[Orange]
Array(Array(orange)).incDiameter(5) // similar to above, we can create
                                    // type class for Array[Array[Orange]]

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

...