все в Scala

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

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

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

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


person missingfaktor    schedule 27.08.2011    source источник
comment
Я недостаточно знаю Scala, но я не уверен на 100%, что в нем есть экзистенциально квантифицированные типы. Может быть, попробуйте немного поискать в Google.   -  person Michael Kohl    schedule 27.08.2011
comment
Кроме того, я нашел это в моем поиске Google, но я не знаю, как его использовать. (На самом деле я не знаю, имеет ли это отношение к проблеме.)   -  person missingfaktor    schedule 27.08.2011
comment
если быть строгим, это расширение GHC, а не чистый стандартизированный Haskell - en.wikibooks.org/wiki/ Haskell/Existentially_quantified_types.   -  person shabunc    schedule 30.08.2011


Ответы (5)


Как прокомментировал @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
comment
ShowBox в вопросе не принимает параметр типа. Весь смысл экзистенциального типа в том, чтобы скрыть этот параметр типа. - person Apocalisp; 28.08.2011
comment
Есть ли способ параметризовать класс типов, чтобы вместо ShowBox у вас было Box[Show] (или, в более общем случае, Box[TC[_]])? Мою попытку можно увидеть на stackoverflow.com/a/28142861/247623, но я не уверен, что это лучший способ . - person Erik Kaplun; 09.02.2015

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

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

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

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

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

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

Значение типа F SuchThat G похоже на значение типа forall a. G a => F a в Haskell. Экземпляр 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)

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

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

person Community    schedule 27.08.2011
comment
Хорошо, но вы начинаете с другого использования forall в Haskell. Тот, что в исходном вопросе, - это экзистенциальная квантификация, а не типы ранга n. - person Miles Sabin; 28.08.2011
comment
Бред какой то. Использование forall здесь является универсальным квантором типа элиминатора. Это экзистенциально по отношению к общему типу данных, но здесь это не представляет интереса. Если обратиться к кодировке Черча, это действительно просто универсальный квантификатор, который Scala поддерживает прямым способом. - person Apocalisp; 28.08.2011
comment
В исходных данных ShowBox = forall ... forall является квантором существования. Вы не отразили это прямо в своем ответе. - person Miles Sabin; 28.08.2011
comment
Да, у меня есть. Фактически, я сделал это точно так же, как Haskell кодирует экзистенциалы: hackage.haskell.org/trac/haskell-prime/wiki/ - person Apocalisp; 28.08.2011
comment
scala› type X[A, B] = A Tuple2 B определенный псевдоним типа X Holy cow. Я никогда раньше не видел, чтобы конструкторы бинарных типов использовались подобным образом. Потрясающий. - person Kris Nuttycombe; 28.08.2011
comment
Сногсшибательный ответ. Большое спасибо! - person missingfaktor; 28.08.2011
comment
@ Крис А ты нет? А как насчет <:<? - person Daniel C. Sobral; 28.08.2011
comment
@ Даниэль, о да, верно. Думаю, я просто редко рассматриваю текстовые имена для такой обработки; имена операторов гораздо более естественны. - person Kris Nuttycombe; 01.09.2011
comment
@Miles Sabin: универсальная и экзистенциальная количественная оценка являются двойственными по различным вещам, которые напоминают отрицание, например, являются аргументом функции. Черч-кодирование представляет данные как квази-CPS-преобразование каждого совпадения с шаблоном, которое перемещает любые экзистенциалы в дважды контравариантное положение. Поднятие квантора существования на один уровень наружу дает вам (полностью эквивалентный) квантор всеобщности в контравариантном положении. Таким образом, полиморфизм четного ранга в основном является синонимом экзистенциальных типов. - person C. A. McCann; 14.09.2011
comment
@С. Э. Макканн, я знаю об этом. Я по-прежнему утверждаю, что решение, которое начинается с того же экзистенциального, что и исходный Haskell, является более прямым кодированием проблемы, чем решение, которое начинается с универсального и возвращается к нему. В конце концов, все изоморфно всему остальному при правильном отношении эквивалентности, так что, как всегда, YMMV. - person Miles Sabin; 15.09.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)
person Kim Stebel    schedule 27.08.2011
comment
Скажем, у меня есть различные типы данных, единственное общее между которыми состоит в том, что они удовлетворяют определенным контекстным ограничениям. Я хочу взять набор значений этих типов данных и применить к ним функции, разрешенные их границами контекста. - person missingfaktor; 27.08.2011
comment
Вы не можете этого сделать, потому что границы контекста не являются частью типа. - person Kim Stebel; 27.08.2011
comment
В Haskell их тоже нет. Однако типы ранга 2 позволяют обращаться с ними полиморфно. Мне интересно, возможно ли это и в Scala. - person missingfaktor; 27.08.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))
person paradigmatic    schedule 27.08.2011
comment
Нет, не совсем. В случае с Haskell я могу применять функции к элементам списка на основе наложенных на них ограничений. Я не могу сделать то же самое в Scala (то есть с вашим подходом). - person missingfaktor; 27.08.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 может перевести «монстров типа Haskell» в очень простой.

person MB_    schedule 28.08.2011
comment
Это не изоморфно фрагменту кода, который я опубликовал. На самом деле это даже не близко. - person missingfaktor; 28.08.2011
comment
Портируя parsec3 на Scala, я, наверное, понял ваш вопрос. - person MB_; 31.08.2011