scala haskell forall

forall en Scala



haskell (5)

( Editar : Agregar métodos para mostrar, para responder al comentario).

Creo que puedes obtener lo mismo usando métodos implícitos con límites de contexto:

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

Como se muestra a continuación, en Haskell, es posible almacenar en una lista los valores con tipos heterogéneos con ciertos límites de contexto en ellos:

data ShowBox = forall s. Show s => ShowBox s heteroList :: [ShowBox] heteroList = [ShowBox (), ShowBox 5, ShowBox True]

¿Cómo puedo lograr lo mismo en Scala, preferiblemente sin subtipos?


Como comentó @Michael Kohl, este uso de forall en Haskell es de tipo existencial y se puede replicar exactamente en Scala utilizando el constructo forSome o un comodín. Eso significa que la respuesta de @ paradigmatic es en gran medida correcta.

Sin embargo, falta algo en relación con el original de Haskell, que las instancias de su tipo ShowBox también capturan las instancias de clases Show type correspondientes de una manera que las hace disponibles para su uso en los elementos de la lista incluso cuando el tipo subyacente exacto ha sido cuantificado existencialmente . Su comentario sobre la respuesta de @ paradigmatic sugiere que desea poder escribir algo equivalente al siguiente 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"]

La respuesta de @Kim Stebel muestra la forma canónica de hacer eso en un lenguaje orientado a objetos explotando la subtipificación. En igualdad de condiciones, esa es la manera correcta de ir en Scala. Estoy seguro de que lo sabe, y tengo buenas razones para querer evitar la subtipificación y replicar el enfoque basado en la clase de tipos de Haskell en Scala. Aquí va ...

Tenga en cuenta que en el Haskell anterior a las instancias de clase de tipo Show para Unit, Int y Bool están disponibles en la implementación de la función useShowBox. Si intentamos traducir esto directamente a Scala obtendremos algo así como,

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

y esto no se puede compilar en useShowBox de la siguiente manera,

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

El problema aquí es que, a diferencia del caso Haskell, las instancias Show class type no se propagan desde el argumento ShowBox al cuerpo de la función useShowBox y, por lo tanto, no están disponibles para su uso. Si tratamos de arreglar eso agregando un contexto adicional vinculado a la función useShowBox,

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

esto soluciona el problema dentro de useShowBox, pero ahora no podemos usarlo junto con el mapa en nuestra lista cuantificada existencialmente,

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

Esto se debe a que cuando se usa useShowBox como argumento para la función de mapa, tenemos que elegir una instancia de Show basada en la información de tipo que tenemos en ese punto. Claramente, no hay una sola instancia de Show que haga el trabajo para todos los elementos de esta lista y por lo tanto no se puede compilar (si hubiésemos definido una instancia de Show para Any, entonces habría, pero eso no es lo que estamos después de aquí ... queremos seleccionar una instancia de clase de tipo basada en el tipo más específico de cada elemento de lista).

Para hacer que esto funcione de la misma manera que lo hace en Haskell, tenemos que propagar explícitamente las instancias Show dentro del cuerpo de useShowBox. Eso podría ir así,

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

luego en el REPL,

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

Tenga en cuenta que hemos desagrupado el contexto vinculado en ShowBox para que tengamos un nombre explícito (showInst) para la instancia Show para el valor contenido. Luego, en el cuerpo de useShowBox podemos aplicarlo explícitamente. También tenga en cuenta que la coincidencia de patrón es esencial para garantizar que solo se abra el tipo existencial una vez en el cuerpo de la función.

Como debería ser obvio, esto es mucho más verosímil que el Haskell equivalente, y recomendaría encarecidamente utilizar la solución basada en subtipos en Scala a menos que tenga muy buenas razones para hacer lo contrario.

Editar

Como se señala en los comentarios, la definición Scala de ShowBox anterior tiene un parámetro de tipo visible que no está presente en el original Haskell. Creo que en realidad es bastante instructivo ver cómo podemos rectificar eso usando tipos abstractos.

Primero reemplazamos el parámetro de tipo con un miembro de tipo abstracto y reemplazamos los parámetros del constructor con valores abstractos,

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

Ahora necesitamos agregar el método de fábrica que las clases de casos nos darían de forma gratuita,

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

Ahora podemos usar ShowBox simple donde antes usamos ShowBox [_] ... el miembro de tipo abstracto está jugando el papel del cuantificador existencial para nosotros ahora,

val heteroList: List[ShowBox] = List(ShowBox(()), ShowBox(5), ShowBox(true)) def useShowBox(sb : ShowBox) = { import sb._ showInst.show(t) } heteroList map useShowBox

(Vale la pena señalar que antes de la introducción de explict forSome y wildcards en Scala, esta era exactamente la forma en que representaría los tipos existenciales).

Ahora tenemos lo existencial exactamente en el mismo lugar que en el Haskell original. Creo que esto es lo más cercano a una interpretación fiel que se puede obtener en Scala.


El ejemplo de ShowBox que diste implica un tipo existencial . Estoy cambiando el nombre del constructor de datos de ShowBox a SB para distinguirlo del tipo :

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

Decimos que s es "existencial", pero aquí hay un cuantificador universal que pertenece al constructor de datos SB . Si solicitamos el tipo del constructor SB con forall explícito activado, esto se vuelve mucho más claro:

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

Es decir, un ShowBox realidad se construye a partir de tres cosas:

  1. Un tipo s
  2. Un valor de tipo s
  3. Una instancia de Show s .

Como el tipo s convierte en parte del ShowBox construido, se cuantifica existencialmente . Si Haskell soportara una sintaxis para la cuantificación existencial, podríamos escribir ShowBox como un alias de tipo:

type ShowBox = exists s. Show s => s

Scala sí admite este tipo de cuantificación existencial y la respuesta de Miles proporciona los detalles utilizando un rasgo que consiste exactamente en esas tres cosas de arriba. Pero dado que esta es una pregunta sobre "forall in Scala", hagámoslo exactamente como lo hace Haskell.

Los constructores de datos en Scala no se pueden cuantificar explícitamente con forall. Sin embargo, cada método en un módulo puede ser. Por lo tanto, puede usar eficazmente el polimorfismo de constructor de tipo como cuantificación universal. Ejemplo:

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

Un Forall de tipo Scala Forall[F] , dado algo de F , es entonces equivalente a un tipo de forall a. F a Haskell forall a. F a forall a. F a .

Podemos usar esta técnica para agregar restricciones al argumento de tipo.

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

Un valor de tipo F SuchThat G es como un valor del tipo Haskell forall a. G a => F a forall a. G a => F a . La instancia de G[A] es buscada implícitamente por Scala si existe.

Ahora, podemos usar esto para codificar su 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))

El método ShowBox.apply es el constructor de datos universalmente cuantificado. Puede ver que toma un tipo S , una instancia de Show[S] y un valor de tipo S , al igual que la versión de Haskell.

Aquí hay un ejemplo de uso:

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

Una codificación más directa en Scala podría ser utilizar una clase de caso:

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

Entonces:

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

En este caso, una List[ShowBox] es básicamente equivalente a una List[String] , pero puede usar esta técnica con características distintas de Show para obtener algo más interesante.

Esto es todo usando Show class de Scalaz .


No creo que sea posible una traducción de 1 a 1 de Haskell a Scala aquí. Pero, ¿por qué no quieres usar el subtipado? Si los tipos que desea utilizar (como Int) carecen de un método de presentación, puede agregarlo a través de conversiones implícitas.

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)


Por qué no:

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

Como sugieren las respuestas de las autoridades, a menudo me sorprende que Scala pueda traducir "monstruos de tipo Haskell" a uno muy simple.