Forall в Scala

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

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

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

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

Ответ 1

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

Тем не менее там что-то отсутствует в сравнении с оригиналом Haskell, который является экземпляром своего типа ShowBox, также захватывает соответствующие экземпляры класса Show type таким образом, который делает их доступными для использования в элементах списка, даже когда точный базовый тип был экзистенциально количественно. Ваш комментарий к @paradigmatic answer предполагает, что вы хотите написать что-то эквивалентное следующему 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"]
Ответ на

@Kim Stebel показывает канонический способ сделать это на объектно-ориентированном языке, используя подтипирование. При прочих равных условиях правильный путь в Scala. Я уверен, что вы это знаете, и у вас есть веские причины, чтобы избежать подтипирования и реплицировать подход класса класса 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 не передаются из аргумента 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 [email protected](t) => sb.showInst.show(t)
}

то в REPL,

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

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

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

Edit

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

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

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

Теперь нам нужно добавить метод factory, который классы 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 это было именно то, как вы представляли бы экзистенциальные типы.)

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

Ответ 2

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

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

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

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

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

  • Тип s
  • Значение типа s
  • Пример 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]
}

A Scala type 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. Экземпляр G[A] неявно просматривается Scala, если он существует.

Теперь мы можем использовать это для кодирования вашего 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 может быть использование класса case:

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] в основном эквивалентно a List[String], но вы можете использовать этот метод с чертами, отличными от Show, чтобы получить что-то более интересное.

Все это используется с Show typeclass из Scalaz.

Ответ 3

Я не думаю, что здесь возможен 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([email protected])

scala> l.map(_.show)
res0: List[String] = List(1)

Ответ 4

( Изменить: добавление методов для показа, чтобы ответить на комментарий.)

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

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))

Ответ 5

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

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 может перевести "монстров типа Haskell" в очень простой.