forall in Scala

后端 未结 5 1835
别那么骄傲
别那么骄傲 2020-12-02 05:57

As shown below, in Haskell, it\'s possible to store in a list values with heterogeneous types with certain context bounds on them:

data ShowBox = forall s. S         


        
5条回答
  •  时光取名叫无心
    2020-12-02 06:45

    The ShowBox example you gave involves an existential type. I'm renaming the ShowBox data constructor to SB to distinguish it from the type:

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

    We say s is "existential", but the forall here is a universal quantifier that pertains to the SB data constructor. If we ask for the type of the SB constructor with explicit forall turned on, this becomes much clearer:

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

    That is, a ShowBox is actually constructed from three things:

    1. A type s
    2. A value of type s
    3. An instance of Show s.

    Because the type s becomes part of the constructed ShowBox, it is existentially quantified. If Haskell supported a syntax for existential quantification, we could write ShowBox as a type alias:

    type ShowBox = exists s. Show s => s
    

    Scala does support this kind of existential quantification and Miles's answer gives the details using a trait that consists of exactly those three things above. But since this is a question about "forall in Scala", let's do it exactly like Haskell does.

    Data constructors in Scala cannot be explicitly quantified with forall. However, every method on a module can be. So you can effectively use type constructor polymorphism as universal quantification. Example:

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

    A Scala type Forall[F], given some F, is then equivalent to a Haskell type forall a. F a.

    We can use this technique to add constraints to the type argument.

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

    A value of type F SuchThat G is like a value of the Haskell type forall a. G a => F a. The instance of G[A] is implicitly looked up by Scala if it exists.

    Now, we can use this to encode your 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))
    

    The ShowBox.apply method is the universally quantified data constructor. You can see that it takes a type S, an instance of Show[S], and a value of type S, just like the Haskell version.

    Here's an example usage:

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

    A more direct encoding in Scala might be to use a case class:

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

    Then:

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

    In this case, a List[ShowBox] is basically equivalent to a List[String], but you can use this technique with traits other than Show to get something more interesting.

    This is all using the Show typeclass from Scalaz.

提交回复
热议问题