в Скале - PullRequest
       86

в Скале

48 голосов
/ 27 августа 2011

Как показано ниже, в Haskell можно хранить в списке значения с разнородными типами с определенными границами контекста на них:

data ShowBox = forall s. Show s => ShowBox s

heteroList :: [ShowBox]
heteroList = [ShowBox (), ShowBox 5, ShowBox True]

Как можно добиться того же в Scala, желательно без подтипов?

Ответы [ 5 ]

62 голосов
/ 27 августа 2011

Как прокомментировал @Michael Kohl, такое использование forall в Haskell является экзистенциальным типом и может быть точно воспроизведено в Scala с использованием конструкции forSome или подстановочного знака. Это означает, что ответ @ paradigmatic в значительной степени правильный.

Тем не менее, в оригинале Haskell чего-то не хватает, так как экземпляры его типа ShowBox также захватывают соответствующие экземпляры класса Show таким образом, что делает их доступными для использования в элементах списка, даже когда точный основной тип экзистенциально количественно. Ваш комментарий к ответу @ paradigmatic предполагает, что вы хотите написать что-то эквивалентное следующему Haskell,

data ShowBox = forall s. Show s => ShowBox s

heteroList :: [ShowBox]
heteroList = [ShowBox (), ShowBox 5, ShowBox True]

useShowBox :: ShowBox -> String
useShowBox (ShowBox s) = show s

-- Then in ghci ...

*Main> map useShowBox heteroList
["()","5","True"]

@ Ответ Ким Стебель показывает канонический способ сделать это на объектно-ориентированном языке, используя подтипы. При прочих равных, это правильный путь в Скала. Я уверен, что вы это знаете, и у вас есть веские причины избегать подтипов и повторять подход, основанный на классах типов в Haskell, в Scala. Здесь идет ...

Обратите внимание, что в Haskell выше экземпляры классов типов Show для Unit, Int и Bool доступны в реализации функции useShowBox. Если мы попытаемся напрямую перевести это на Scala, мы получим что-то вроде

trait Show[T] { def show(t : T) : String }

// Show instance for Unit
implicit object ShowUnit extends Show[Unit] {
  def show(u : Unit) : String = u.toString
}

// Show instance for Int
implicit object ShowInt extends Show[Int] {
  def show(i : Int) : String = i.toString
}

// Show instance for Boolean
implicit object ShowBoolean extends Show[Boolean] {
  def show(b : Boolean) : String = b.toString
}

case class ShowBox[T: Show](t:T)

def useShowBox[T](sb : ShowBox[T]) = sb match {
  case ShowBox(t) => implicitly[Show[T]].show(t)
  // error here      ^^^^^^^^^^^^^^^^^^^
} 

val heteroList: List[ShowBox[_]] = List(ShowBox(()), ShowBox(5), ShowBox(true))

heteroList map useShowBox

и это не удается скомпилировать в useShowBox следующим образом:

<console>:14: error: could not find implicit value for parameter e: Show[T]
         case ShowBox(t) => implicitly[Show[T]].show(t)
                                      ^

Проблема здесь в том, что, в отличие от случая с Haskell, экземпляры класса Show type не передаются из аргумента ShowBox в тело функции useShowBox и, следовательно, недоступны для использования. Если мы попытаемся это исправить, добавив дополнительный контекст, связанный с функцией useShowBox,

def useShowBox[T : Show](sb : ShowBox[T]) = sb match {
  case ShowBox(t) => implicitly[Show[T]].show(t) // Now compiles ...
} 

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

scala> heteroList map useShowBox
<console>:21: error: could not find implicit value for evidence parameter
                     of type Show[T]
              heteroList map useShowBox
                             ^

Это потому, что когда useShowBox предоставляется в качестве аргумента функции карты, мы должны выбрать экземпляр Show на основе информации о типе, имеющейся у нас на тот момент. Ясно, что не существует только одного экземпляра Show, который будет выполнять работу для всех элементов этого списка, и поэтому его не удается скомпилировать (если бы мы определили экземпляр Show для Any, тогда это было бы, но это не то, что мы делаем после здесь ... мы хотим выбрать экземпляр класса типа на основе наиболее определенного типа каждого элемента списка).

Чтобы заставить это работать так же, как в Haskell, мы должны явно распространять экземпляры Show в теле useShowBox. Это может пойти так,

case class ShowBox[T](t:T)(implicit val showInst : Show[T])

val heteroList: List[ShowBox[_]] = List(ShowBox(()), ShowBox(5), ShowBox(true))

def useShowBox(sb : ShowBox[_]) = sb match {
  case sb@ShowBox(t) => sb.showInst.show(t)
}

затем в REPL

scala> heteroList map useShowBox
res7: List[String] = List((), 5, true)

Обратите внимание, что мы удалили контекст, связанный с ShowBox, чтобы у нас было явное имя (showInst) для экземпляра Show для содержащегося в нем значения. Тогда в теле useShowBox мы можем явно применить его. Также обратите внимание, что сопоставление с образцом необходимо для обеспечения того, чтобы мы открывали экзистенциальный тип только один раз в теле функции.

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

Редактировать

Как отмечено в комментариях, определение ShowBox в Scala выше имеет видимый параметр типа, которого нет в оригинале Haskell. Я думаю, что на самом деле весьма поучительно посмотреть, как мы можем исправить это, используя абстрактные типы.

Сначала мы заменим параметр типа на абстрактный член типа и заменим параметры конструктора на абстрактные значения,

trait ShowBox {
  type T
  val t : T
  val showInst : Show[T]
}

Теперь нам нужно добавить метод фабрики, который в противном случае классы case предоставили бы нам бесплатно,

object ShowBox {
  def apply[T0 : Show](t0 : T0) = new ShowBox {
    type T = T0
    val t = t0
    val showInst = implicitly[Show[T]]
  } 
}

Теперь мы можем использовать обычный ShowBox, где бы мы ни использовали ранее ShowBox [_] ... член абстрактного типа теперь играет для нас роль экзистенциального квантификатора,

val heteroList: List[ShowBox] = List(ShowBox(()), ShowBox(5), ShowBox(true))

def useShowBox(sb : ShowBox) = {
  import sb._
  showInst.show(t)
}

heteroList map useShowBox

(Стоит отметить, что до появления в Scala явлений forSome и подстановочных знаков именно так вы и представляли экзистенциальные типы.)

Теперь у нас есть экзистенциал точно в том же месте, что и в оригинальном Хаскеле. Я думаю, что это настолько близко к верному исполнению, как вы можете получить в Scala.

24 голосов
/ 27 августа 2011

Пример ShowBox, который вы привели, включает экзистенциальный тип .Я переименовываю конструктор данных ShowBox в SB, чтобы отличить его от типа :

data ShowBox = forall s. Show s => SB s

Мы говорим s "экзистенциально", но forall здесь представлен универсальный квантификатор, относящийся к конструктору данных SB.Если мы спросим тип конструктора SB с включенным явным forall, это станет намного понятнее:

SB :: forall s. Show s => s -> ShowBox

То есть ShowBox фактически состоит из трех вещей:

  1. Тип s
  2. Значение типа s
  3. Экземпляр Show s.

Поскольку типs становится частью построенного ShowBox, это экзистенциально количественно .Если бы Haskell поддерживал синтаксис для экзистенциальной квантификации, мы могли бы написать ShowBox в качестве псевдонима типа:

type ShowBox = exists s. Show s => s

Scala поддерживает этот вид экзистенциальной квантификации, и ответ Майлза дает подробности, используя черту, которая состоит изэти три вещи выше.Но так как это вопрос о "forall в Scala", давайте сделаем это точно так же, как это делает Haskell.

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

trait Forall[F[_]] {
  def apply[A]: F[A]
}

Тип Scala Forall[F], с учетом некоторого F, затем эквивалентен типу Haskell forall a. F a.

Мы можем использовать эту технику для добавления ограничений каргумент типа.

trait SuchThat[F[_], G[_]] {
  def apply[A:G]: F[A]
}

Значение типа F SuchThat G аналогично значению типа Haskell forall a. G a => F a.Scala неявно просматривает экземпляр G[A], если он существует.

Теперь мы можем использовать его для кодирования вашего ShowBox ...

import scalaz._; import Scalaz._ // to get the Show typeclass and instances

type ShowUnbox[A] = ({type f[S] = S => A})#f SuchThat Show

sealed trait ShowBox {
  def apply[B](f: ShowUnbox[B]): B  
}

object ShowBox {
  def apply[S: Show](s: => S): ShowBox = new ShowBox {
    def apply[B](f: ShowUnbox[B]) = f[S].apply(s)
  }
  def unapply(b: ShowBox): Option[String] =
    b(new ShowUnbox[Option[String]] {
      def apply[S:Show] = s => some(s.shows)
  })
}

val heteroList: List[ShowBox] = List(ShowBox(()), ShowBox(5), ShowBox(true))

ShowBox.applyМетод является универсально количественным конструктором данных.Вы можете видеть, что он принимает тип S, экземпляр Show[S] и значение типа S, так же, как версия на Haskell.

Вот пример использования:

scala> heteroList map { case ShowBox(x) => x }
res6: List[String] = List((), 5, true)

Более прямое кодирование в Scala может заключаться в использовании класса дела:

sealed trait ShowBox
case class SB[S:Show](s: S) extends ShowBox {
  override def toString = Show[S].shows(s)
}

Тогда:

scala> val heteroList = List(ShowBox(()), ShowBox(5), ShowBox(true))
heteroList: List[ShowBox] = List((), 5, true)

В этом случае a List[ShowBox] в основном эквивалентно List[String], но вы можете использовать эту технику с чертами, отличными от Show, чтобы получить что-то более интересное.

Все это использует класс типов Show из Scalaz.

5 голосов
/ 27 августа 2011

Я не думаю, что здесь возможен перевод 1-к-1 из Haskell в Scala.Но почему вы не хотите использовать подтипы?Если у типов, которые вы хотите использовать (например, Int), отсутствует метод show, вы все равно можете добавить его с помощью неявных преобразований.

scala> trait Showable { def show:String }
defined trait Showable

scala> implicit def showableInt(i:Int) = new Showable{ def show = i.toString }
showableInt: (i: Int)java.lang.Object with Showable

scala> val l:List[Showable] = 1::Nil
l: List[Showable] = List($anon$1@179c0a7)

scala> l.map(_.show)
res0: List[String] = List(1)
3 голосов
/ 27 августа 2011

( Редактировать : Добавление методов для отображения, чтобы ответить на комментарий.)

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

trait Show[T] {
  def apply(t:T): String
}
implicit object ShowInt extends Show[Int] {
  def apply(t:Int) = "Int("+t+")"
}
implicit object ShowBoolean extends Show[Boolean] {
  def apply(t:Boolean) = "Boolean("+t+")"
}

case class ShowBox[T: Show](t:T) {
  def show = implicitly[Show[T]].apply(t)
}

implicit def box[T: Show]( t: T ) =
  new ShowBox(t)

val lst: List[ShowBox[_]] = List( 2, true )

println( lst ) // => List(ShowBox(2), ShowBox(true))

val lst2 = lst.map( _.show )

println( lst2 ) // => List(Int(2), Boolean(true))
0 голосов
/ 28 августа 2011

Почему бы и нет:

trait ShowBox {
    def show: String
}

object ShowBox {
    def apply[s](x: s)(implicit i: Show[s]): ShowBox = new ShowBox {
        override def show: String = i.show(x)
    }
}

Как показывают ответы властей, я часто удивляюсь, что Scala может перевести "монстров типа Хаскелла" в очень простое.

...