форалл в Scala

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

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

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

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

5 ответов


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

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

обратите внимание, что в Haskell выше экземпляров класса Show type для 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, тип шоу экземпляры класса не распространяются из аргумента ShowBox в тело функции useShowBox и, следовательно, недоступны для использования. Если мы попытаемся исправить это, добавив дополнительный контекст, связанный с функцией useShowBox,

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

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

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 для любого, тогда было бы, но это не то, что мы здесь ищем ... мы хотим выбрать экземпляр класса типа на основе наиболее конкретного типа каждого элемента списка).

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

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

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

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

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

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

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

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


на 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 in Scala", давайте сделаем это точно так же, как это делает Хаскелл.

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

это все с помощью Show typeclass от Scalaz.


Я не думаю, что перевод 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@179c0a7)

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

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

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

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

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

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