Как прокомментировал @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"]
Ответ @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, но теперь мы не можем использовать его вместе с 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 для 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)
}
затем в РЕПЛ,
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
(Стоит отметить, что до введения explict forSome и подстановочных знаков в Scala именно так вы представляли экзистенциальные типы.)
Теперь у нас есть экзистенциал точно в том же месте, что и в оригинальном Haskell. Я думаю, что это самое близкое к точному воспроизведению, которое вы можете получить в Scala.
person
Miles Sabin
schedule
27.08.2011