Рекурсивный тип Scala и реализация конструктора типов

У меня есть ситуация, когда мне нужен метод, который может принимать типы:

Array[Int]
Array[Array[Int]]
Array[Array[Array[Int]]]
Array[Array[Array[Array[Int]]]]
etc...

давайте назовем этот тип RAI для «рекурсивного массива целых чисел»

def make(rai: RAI): ArrayPrinter = { ArrayPrinter(rai) }

Где ArrayPrinter — это класс, который инициализируется с помощью RAI и выполняет итерацию по всему rai (скажем, он печатает все значения в этом Array[Array[Int]])

val arrayOfArray: Array[Array[Int]] = Array(Array(1, 2), Array(3, 4))
val printer: ArrayPrinter[Array[Array[Int]]] = make(arrayOfArray)
printer.print_! // prints "1, 2, 3, 4" 

Он также может возвращать исходный массив [Array[Int]] без потери информации о типе.

val arr: Array[Array[Int]] = printer.getNestedArray() 

Как вы реализуете это в Scala?


person Michael Lafayette    schedule 20.05.2017    source источник
comment
Похоже на работу для index.scala-lang.org/slamdata /matryoshka/matryoshka-core/0.18.3   -  person Reactormonk    schedule 21.05.2017
comment
Одни только схемы стоят проверки матрешки!   -  person pedrofurla    schedule 21.05.2017


Ответы (2)


Давайте сначала сосредоточимся на типе. Согласно вашему определению, тип T должен проверять тип в качестве аргумента для ArrayPrinter, принимается ли он следующей функцией типа:

def accept[T]: Boolean =
  T match { // That's everyday business in agda
    case Array[Int] => true
    case Array[X]   => accept[X]
    case _          => false
  }

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

trait RAI[T]

object RAI {
  implicit val e0: RAI[Array[Int]] = null
  implicit def e1[T](implicit i: RAI[T]): RAI[Array[T]] = null
}

case class ArrayPrinter[T: RAI](getNestedArray: T) // Only compiles it T is a RAI

Для печати самое простое решение - рассматривать rai: T как rai: Any:

def print_!: Unit = {
  def print0(a: Any): Unit = a match {
    case a: Int      => println(a)
    case a: Array[_] => a.foreach(print0)
    case _           => ???
  }
}

Вы также можете пофантазировать и написать print_!, используя классы типов, но это, вероятно, будет менее эффективно и займет больше времени, чем описанное выше... Оставлено в качестве упражнения для читателя ;-)

person OlivierBlanvillain    schedule 20.05.2017

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

В вашем случае это может выглядеть так:

abstract class ArrayPrinter[A] {
  def mkString(a: A): String
}
implicit object BaseArrayPrinter extends ArrayPrinter[Int] {
  override def mkString(x: Int) = x.toString
}
class WrappedArrayPrinter[A](wrapped: ArrayPrinter[A]) extends ArrayPrinter[Array[A]] {
  override def mkString(xs: Array[A]) = xs.map(wrapped.mkString).mkString(", ")
}
implicit def makeWrappedAP[A](implicit wrapped: ArrayPrinter[A]): ArrayPrinter[Array[A]] = new WrappedArrayPrinter(wrapped)

def printHello[A](xs: A)(implicit printer: ArrayPrinter[A]): Unit = {
  println("hello, array: " + printer.mkString(xs))
}

Это, как правило, немного чище, чем если бы этот класс RAIOps (или ArrayPrinter) принимал объект как часть своего конструктора. Это обычно приводит к большему количеству «упаковки» и «распаковки», сложных подписей типов, странного сопоставления с образцом и т. д.

У него также есть дополнительное преимущество, заключающееся в том, что его легче расширять. Если позже у кого-то еще возникнет причина захотеть реализовать ArrayPrinter для Set[Int], он может определить его локально в своем коде. Я много раз определял пользовательский Ordering.

person Joe K    schedule 23.05.2017