Шаблон класса Scala Type против сопоставления с образцом или перегрузки - PullRequest
0 голосов
/ 16 мая 2018

Я изо всех сил пытаюсь придумать хорошую ментальную модель для , когда проблема хорошо подходит для шаблона Типовых классов?

Недавно я работал с такой моделью, как

sealed trait FooBar
case class Foo() extends FooBar
case class Bar() extends FooBar

Интуитивно я бы просто совпал с шаблоном

def handle(x: FooBar) = x match {
  case f: Foo => println("foo")
  case b: Bar => println("bar")
}

или явно использовал бы подтип / перегрузку, например

object Overloading {
  def handle(x: Foo) = println("foo")
  def handle(x: Bar) = println("bar")
}

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

trait FooBarThing[T <: FooBar] {
  def handle(x: T): Unit
}

object TypeClass {

  implicit object HandleFoo extends FooBarThing[Foo] {
    def handle(x: Foo) = println("foo")
  }

  implicit object HandleBar extends FooBarThing[Bar] {
    def handle(x: Bar) = println("bar")
  }

  def process[T <: FooBar](x: T)(implicit ev: FooBarThing[T]): Unit = {
    ev.handle(x)
  }
}

Я нашел много статей, объясняющих как писать классы типов, но не так много для , когда

Ответы [ 2 ]

0 голосов
/ 16 мая 2018

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

Как правило, перегрузка создает много хлопот для систем типов и затрудняет использование последствий. В вопросе о SO обсуждаются другие недостатки перегрузки, но среди прочего есть:

  • Трудно поднять метод для функции
  • Неоднозначность в применении неявных представлений каргументы перегруженной функции.

Я бы использовал перегрузку только в случае той же функциональности, предоставляемой несвязанным типам, для создания более приятного опыта программиста, например

object Printer {
    def print(a: Bool): String = ???
    def print(a: Int): String = ???
}

Поскольку вы можете создавать паттернысовпадать по подтипам, я бы наверняка использовал эту опцию.

0 голосов
/ 16 мая 2018

Шаблон класса типов дает возможность реализовать специальный полиморфизм . То есть, если у вас есть какая-то полиморфная функция foobar, которая должна работать со многими различными типами T, а затем у вас есть конкретный тип T1, который не реализует интерфейс, обеспечивающий foobar, вы можете присоединить foobar до T1 специальным образом следующим образом:

trait FoobarTypeclass[T] {
  def foobar(t: T): Unit
}

def functionThatRequiresFoobar[T: FoobarTypeclass](t: T): Unit = {
  for (i <- 1 to 10) 
    implicitly[FoobarTypeclass[T]].foobar(t)
}

// note that `functionThatRequiresFoobar` knows nothing about `T1` at this point

class T1
implicit object AdHocFoobarForT1 extends FoobarTypeclass[T1] {
  def foobar(t: T1): Unit  = println("foobar now works on T1, awesome!")
}

functionThatRequiresFoobar(new T1) // but here, it works anyway!

В приведенном выше примере вы видите две вещи:

  1. Ни FoobarTypeclass, ни functionThatRequiresFoobar не должны ничего знать о существовании конкретного типа T1
  2. Тип T1 также не должен ничего знать о FoobarTypeclass или functionThatRequiresFoobar.

Это означает, что T1 и functionThatRequiresFoobar полностью отделены . Но в последней строке примера,

functionThatRequiresFoobar(new T1)

в любом случае работает просто замечательно, потому что класс типов AdHocFoobarForT1 присоединяет реализацию foobar к классу T1 специальным образом.

Аналогичным образом, вы можете использовать этот шаблон для «реализации интерфейсов в режиме ad-hoc» в классах, которые не объявляют никаких соответствующих интерфейсов в своей иерархии наследования. Это, в свою очередь, позволяет вам склеивать совершенно независимые библиотеки, просто предоставляя несколько классов типов здесь и там.

...