What does the `forall` keyword in Haskell/GHC do?

前端 未结 8 1669
[愿得一人]
[愿得一人] 2020-11-28 17:01

I\'m beginning to understand how the forall keyword is used in so-called \"existential types\" like this:

data ShowBox = forall s. Show s =>          


        
8条回答
  •  粉色の甜心
    2020-11-28 17:47

    Let's start with a code example:

    foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
    foob postProcess onNothin onJust mval =
        postProcess val
        where
            val :: b
            val = maybe onNothin onJust mval
    

    This code doesn't compile (syntax error) in plain Haskell 98. It requires an extension to support the forall keyword.

    Basically, there are 3 different common uses for the forall keyword (or at least so it seems), and each has its own Haskell extension: ScopedTypeVariables, RankNTypes/Rank2Types, ExistentialQuantification.

    The code above doesn't get a syntax error with either of those enabled, but only type-checks with ScopedTypeVariables enabled.

    Scoped Type Variables:

    Scoped type variables helps one specify types for code inside where clauses. It makes the b in val :: b the same one as the b in foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b.

    A confusing point: you may hear that when you omit the forall from a type it is actually still implicitly there. (from Norman's answer: "normally these languages omit the forall from polymorphic types"). This claim is correct, but it refers to the other uses of forall, and not to the ScopedTypeVariables use.

    Rank-N-Types:

    Let's start with that mayb :: b -> (a -> b) -> Maybe a -> b is equivalent to mayb :: forall a b. b -> (a -> b) -> Maybe a -> b, except for when ScopedTypeVariables is enabled.

    This means that it works for every a and b.

    Let's say you want to do something like this.

    ghci> let putInList x = [x]
    ghci> liftTup putInList (5, "Blah")
    ([5], ["Blah"])
    

    What must be the type of this liftTup? It's liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b). To see why, let's try to code it:

    ghci> let liftTup liftFunc (a, b) = (liftFunc a, liftFunc b)
    ghci> liftTup (\x -> [x]) (5, "Hello")
        No instance for (Num [Char])
        ...
    ghci> -- huh?
    ghci> :t liftTup
    liftTup :: (t -> t1) -> (t, t) -> (t1, t1)
    

    "Hmm.. why does GHC infer that the tuple must contain two of the same type? Let's tell it they don't have to be"

    -- test.hs
    liftTup :: (x -> f x) -> (a, b) -> (f a, f b)
    liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)
    
    ghci> :l test.hs
        Couldnt match expected type 'x' against inferred type 'b'
        ...
    

    Hmm. so here GHC doesn't let us apply liftFunc on v because v :: b and liftFunc wants an x. We really want our function to get a function that accepts any possible x!

    {-# LANGUAGE RankNTypes #-}
    liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
    liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)
    

    So it's not liftTup that works for all x, it's the function that it gets that does.

    Existential Quantification:

    Let's use an example:

    -- test.hs
    {-# LANGUAGE ExistentialQuantification #-}
    data EQList = forall a. EQList [a]
    eqListLen :: EQList -> Int
    eqListLen (EQList x) = length x
    
    ghci> :l test.hs
    ghci> eqListLen $ EQList ["Hello", "World"]
    2
    

    How is that different from Rank-N-Types?

    ghci> :set -XRankNTypes
    ghci> length (["Hello", "World"] :: forall a. [a])
        Couldnt match expected type 'a' against inferred type '[Char]'
        ...
    

    With Rank-N-Types, forall a meant that your expression must fit all possible as. For example:

    ghci> length ([] :: forall a. [a])
    0
    

    An empty list does work as a list of any type.

    So with Existential-Quantification, foralls in data definitions mean that, the value contained can be of any suitable type, not that it must be of all suitable types.

提交回复
热议问题