Вопрос о типах классов в Scala - PullRequest
5 голосов
/ 12 апреля 2011

Пусть есть классы Fruit, Orange и Apple.

abstract class Fruit
class Orange extends Fruit
class Apple extends Fruit

Теперь я хочу добавить функциональность write к обоим типам Orange и Apple.Используя шаблон класса , я могу сделать следующее:

trait Writer[T] {def write(t:T)}

implicit object AppleWriter extends Writer[Apple] {
   def write(a:Apple) {println("I am an apple!")} 
}

implicit object OrangeWriter extends Writer[Orange] {
   def write(o:Orange) {println("I am an orange!")} 
}

def write[T](t:T)(implicit w:Writer[T]){w.write(t)}

Так что, хорошо, но что, если я хочу определить writeFruits?

def writeFruits(fruits:List[Fruit]) {for (fruit <- fruits) write(fruit)}

Я бы хотел writeFruits звонить либо write[Apple], либо write[Orange] для каждого fruit.Я вижу, что это не работает (и я знаю, почему), но может быть Я могу реализовать writeFruits в любом случае.

Могу ли я реализовать writeFruits как-нибудь?

Ответы [ 4 ]

6 голосов
/ 12 апреля 2011

В случае ковариантных / контравариантных типов вам почти необходимо определить класс типов для «базового» типа здесь:

implicit object FruitWriter extends Writer[Fruit] {
  def write(a : Fruit) = a match {
    case _ : Apple => println("I am an apple!")
    case _ : Orange => println("I am an orange")
  }
}

Вы также можете работать над определением класса типов с дисперсией, чтобыWriter [Fruit] можно использовать, когда вам нужен Writer [Apple].К сожалению, но если вы хотите использовать ОО-полиморфизм, вы должны закодировать его в функциональные аспекты.

* сильный текст * Другой вариант - использовать HList для записи плодов ивыполнить всю типовую рекурсию самостоятельно ...

Предполагая:

trait HList
object HNil extends HList
case class ::[A, Rest <: HList](head : A, tail : Rest)

Тогда мы можем сделать что-нибудь веселое, например:

implicit def nilWriter = new Writer[HNil] = { def write(o : HNil) = () }
implicit def hlistWriter[A, Rest](implicit aw : Writer[A], rw : Writer[Rest]) =
  new Writer[A :: Rest] {
  def write(o : (A :: Rest)) = {
    aw.write(o.head)
    rw.write(o.tail)
  }
}

СЕЙЧАС

write( new Orange :: new Apple :: HNil)

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

4 голосов
/ 12 апреля 2011

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

def writeOne[T](t:T)(implicit w:Writer[T]){w.write(t)}  // New name to avoid shadowing

implicit object FruitWriter extends Writer[Fruit] {
  def write(f: Fruit) { f match {
    case o: Orange => writeOne(o)
    case a: Apple => writeOne(a)
  }}
}

scala> val fruits = List(new Apple, new Orange)
fruits: List[Fruit] = List(Apple@1148ab5c, Orange@39ea2de1)

scala> for (fruit <- fruits) writeOne(fruit)
I am an apple!
I am an orange!
2 голосов
/ 12 апреля 2011

А может кейс-классы для вас?

abstract class Fruit {}
case object Orange extends Fruit
case object Apple extends Fruit

trait Writer[T] {def write (t:T)}

implicit object FruitWriter extends Writer [Fruit] {
   def write (fruit: Fruit) = fruit match { 
     case Apple => println ("I am an apple!")
     case Orange => println ("I am an orange!")
   } 
}

def writeFruits (fruits: List[Fruit]) {
  for (fruit <- fruits) write(fruit)
}

val fl = List (Orange, Apple, Apple, Orange, Apple)    

writeFruits (fl)                                       
I am an orange!
I am an apple!
I am an apple!
I am an orange!
I am an apple!
1 голос
/ 13 апреля 2011

Это не совсем то, что вы хотите, но дает вам большую свободу для создания вашего hiearchy:

sealed trait Fruit

case class Orange extends Fruit with OrangeWriter 
case class Apple extends Fruit
case class Banana extends Fruit

trait Writer {
  def write()
}

trait AppleWriter extends Writer {
  self: Apple =>
  def write() {println("I am an apple!")}
}

trait OrangeWriter extends Writer {
  self: Orange =>
  def write() {println("I am an orange!")}
}

def writeFruits(fruits:List[Fruit]) {
  fruits.collect{case w:Writer => w}.foreach(_.write())
}

writeFruits(List(Apple(), Orange(),Banana(), new Apple with AppleWriter))

Как вы можете видеть, вы можете иметь Fruit s, к которым всегда прикреплен Writer (здесь Orange s), и вы можете прикреплять Writers "на лету" (последний Apple в List).

...