代码之家  ›  专栏  ›  技术社区  ›  JUST MY correct OPINION

Haskell/GHC中的'forall'关键字是做什么的?

  •  273
  • JUST MY correct OPINION  · 技术社区  · 16 年前

    我开始明白 forall 关键字用于所谓的“存在类型”,如:

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

    福尔 我根本无法将我的思想用在像这样的事情上:

    runST :: forall a. (forall s. ST s a) -> a
    

    或者解释为什么它们不同:

    foo :: (forall a. a -> a) -> (Char, Bool)
    bar :: forall a. ((a -> a) -> (Char, Bool))
    

    或者整个 RankNTypes 东西。。。

    1. 它们是不完整的。他们解释了这个关键字的一部分用法(比如“存在主义类型”),这让我很高兴,直到我阅读了以完全不同的方式使用它的代码(比如 runST , foo 和 bar 上面)。
    2. 它们密密麻麻地塞满了假设,我读过离散数学、范畴论或抽象代数这周最新流行的分支(如果我从来没有读过“查阅报纸” 无论什么 关于实施的细节,请再说一遍,为时过早。)

    所以。。。

    福尔 用简单明了的英语表达的关键字(或者,如果它存在于某个地方,指向我错过的如此清晰的解释)不认为我是一个深谙行话的数学家?


    编辑以添加:

    下面的高质量答案中有两个非常突出,但不幸的是,我只能选择一个最好的。 Norman's answer 是详细和有用的,解释事情的方式,显示了一些理论基础 福尔 同时向我展示了它的一些实际意义。 yairchu's answer 涵盖了其他人未提及的领域(范围类型变量),并用代码和GHCi会话说明了所有概念。如果能把两者都选为最好的,我会的。不幸的是,我不能,在仔细研究了这两个答案后,我决定yairchu的略胜一筹诺曼的,因为说明性的代码和附加的解释。然而,这有点不公平,因为我真的需要两个答案来理解这一点 福尔 当我在打字签名中看到它时,不会让我有一丝恐惧感。

    8 回复  |  直到 7 年前
        1
  •  289
  •   Micha Wiedenmann Lieven Keersmaekers    7 年前

    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
    

    这段代码没有在纯Haskell 98中编译(语法错误)。它需要一个扩展来支持 forall 关键字。

    不同的 一般用途 福尔 似乎 ScopedTypeVariables , RankNTypes / Rank2Types , ExistentialQuantification .

    上面的代码在启用这两个选项的情况下都不会得到语法错误,但是只会使用 ScopedTypeVariables范围类型变量 启用。

    作用域类型变量:

    作用域类型变量有助于指定内部代码的类型 where 条款。它使 b val :: b 和那个一样 在里面 foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b .

    令人困惑的一点 :当您省略 福尔 从一个类型来看,它实际上仍然隐式地存在( from Norman's answer: "normally these languages omit the forall from polymorphic types" ). 这个说法是正确的, 但是 它指的是 福尔 ,而不是 使用。

    排名类型:

    让我们从这个开始 mayb :: b -> (a -> b) -> Maybe a -> b 相当于 mayb :: forall a b. b -> (a -> b) -> Maybe a -> b , 除了 ScopedTypeVariables范围类型变量 已启用。

    这意味着它适用于所有 a b .

    假设你想做这样的事。

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

    liftTup ? 它是 liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)

    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)
    

    “嗯。。为什么GHC推断元组必须包含两个相同类型的元组?让我们告诉它,他们不必

    -- 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'
        ...
    

    嗯。所以GHC不允许我们申请 liftFunc 在 v 因为 v :: b 和 提升管 想要一个 x . 我们真的希望我们的函数能得到一个接受任何可能的 十

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

    所以不是 提升 对所有人都有效 ,是它得到的函数。

    举个例子:

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

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

    forall a 意思是你的表达必须符合所有可能的条件 一 s。例如:

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

    空列表可以作为任何类型的列表使用。

    福尔 s在 data 可以 任何 合适的类型,不是说它 必须 属于 全部的

        2
  •  128
  •   lledr Norman Ramsey    7 年前

    有人能吗 完全地

    forall :

    • 它是一个量词。你必须至少有一点逻辑(谓词演算)才能看到一个普遍的或存在的量词。如果你从未见过谓词演算或者对量词不太适应(我也见过博士资格考试期间不太适应的学生),那么对你来说,没有简单的解释 .

    • 类型 System F 练习写多态类型,你会发现 福尔 令人困惑。使用Haskell或ML的经验是不够的,因为通常这些语言忽略了

    • 尤其是在哈斯克尔, 使用的方式让我感到困惑(我不是类型理论家,但我的工作让我接触到了 对我来说,造成困惑的主要原因是 福尔 是用来编码一种我自己更喜欢用的类型 exists

      如果您对类型同构的概念不熟悉,或者您没有任何关于类型同构的实践,那么 福尔 会妨碍你的。

    • 而一般的概念 福尔 类型和编程语言 ,这是一本非常好的书。

    至于你的例子,

    • runST 应该 朗斯特 : "Lazy Functional State Threads" 朗斯特

    • foo :: (forall a. a -> a) -> (Char,Bool)
      bar :: forall a. ((a -> a) -> (Char, Bool))
      

      如果我打电话 bar a 我喜欢,我可以从类型中传递一个函数 一 键入 一 . 例如,我可以传递函数 (+1) 或函数 reverse . 你可以想到 福尔 就像说“我现在就要选类型了”(选择类型的技术术语是 例示

      foo 更为严格的是 福 是多态函数。对于这种类型,我只能传递给 福 是 id 或者一个总是发散或出错的函数,比如 undefined . 原因是 福尔 福 我不能选择什么 一 更确切地说是 实施 属于 福 是。因为 在箭头的左侧,而不是在箭头的上方 酒吧 ,实例化发生在函数体中,而不是在调用位置。

    A 完成 解释 福尔 朗斯特


    附录: 行话“上面”,“下面”,“左边”。这些和 类型的编写方式以及与抽象语法树有关的一切。在抽象语法中 福尔

    示例:

    • forall a . [a] -> [a] ,孔在箭头上方;箭头左边的是 [a] .

    • 在

      forall n f e x . (forall e x . n e x -> f -> Fact x f) 
                    -> Block n e x -> f -> Fact x f
      

      括号中的类型称为“箭头左侧的孔”(我正在处理的优化器中使用这样的类型。)

        3
  •  54
  •   Community Mohan Dere    9 年前

    我最初的回答是:

    正如诺曼所指出的,很难用简单明了的英语给出清楚的解释

    “福尔”只有一件事需要记住: 一些范围 使用“left”/“above”的概念来表达相同的范围概念 his excellent answer .

    “forall”的大多数用法都非常简单,您可以在中找到它们的介绍 the GHC Users Manual, S7.8 尤其是 the excellent S7.8.5 关于嵌套

    在Haskell中,当类型为

    length :: forall a. [a] -> Int
    

    length :: [a] -> Int
    

    就这样。

    因为现在可以将类型变量绑定到某个作用域,所以可以使用其他作用域 比最高级别(“ universally quantified 例如你的第一个例子, 其中类型变量仅在数据结构中可见。这允许 对于隐藏类型(“ existential types arbitrary nesting 绑定类型(“秩N类型”)。

    要深入理解类型系统,您需要学习一些行话。那是 计算机科学的本质。然而,像上面这样的简单用法应该是 很好的介绍是 Launchbury and Peyton Jones

        4
  •  34
  •   Apocalisp    6 年前

    这里有一个简单明了的解释,你可能已经很熟悉了。

    这个 forall 关键字在Haskell中只有一种用法。当你看到它的时候,它的意思总是一样的。

    A 普遍量化类型 forall a. f a . 这种类型的值可以看作 那得花点时间 类型 a 作为其参数并返回 价值 f a . 但在Haskell中,这些类型参数是由类型系统隐式传递的。这个“函数”必须给你相同的值,不管它接收的是哪种类型,所以这个值是 多态的 .

    forall a. [a] . 该类型的值采用另一种类型 并返回相同类型的元素列表 . 当然,只有一种可能的实现。它必须给你一张空名单,因为 一 完全可以是任何类型。空列表是其元素类型中唯一多态的列表值(因为它没有元素)。

    或者类型 forall a. a -> a 和类型的值 一 . 然后,实现必须返回相同类型的值 一 . 只有一种可能的实现。它必须返回与给定值相同的值。

    存在量化

    安 存在量化类型 exists a. f a ,如果Haskell支持这种表示法的话。这种类型的值可以看作 一双 (或“产品”)由 一 和类型的值 法国 .

    exists a. [a] ,您有一个某种类型的元素列表。它可以是任何类型,但是即使你不知道它是什么,你也可以对这样一个列表做很多事情。您可以反转它,或者计算元素的数量,或者执行任何其他不依赖于元素类型的列表操作。

    福尔 表示“存在”类型,如下面所示?

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

    它可能会让人困惑,但它实际上是在描述 SB

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

    ShowBox s s . 换句话说,它是一个存在量化类型的值。 秀霸 exists s. Show s => s ,如果Haskell支持这种表示法的话。

    runST 还有朋友

    foo :: (forall a. a -> a) -> (Char,Bool)
    bar :: forall a. ((a -> a) -> (Char, Bool))
    

    我们先来看看 bar . 它需要一个类型 一 和一个函数类型 a -> a ,并生成类型为的值 (Char, Bool) . 我们可以选择 Int 作为 一 Int -> Int 例如。但是 foo 是不同的。它要求执行 能够将它想要的任何类型传递给我们赋予它的函数。所以我们能合理地赋予它的唯一功能是 id .

    我们现在应该能够解决 朗斯特 :

    runST :: forall a. (forall s. ST s a) -> a
    

    所以呢 朗斯特 一 ,不管我们给什么类型的 . 为此,它使用类型为 forall s. ST s a 一定会产生 一 . 更重要的是,它必须能够产生一个类型的值 一 朗斯特 决定给予 s .

    朗斯特 一 不能包含类型 完全。不能将类型的值传递给它 ST s [s] 朗斯特 可以使用类型的值自由执行变异 s . 这个类型保证了这个变异对于 朗斯特

    类型 朗斯特 秩2多态型 因为它的参数类型包含 量词。类型 福 一种普通的多态类型,如 酒吧 ,是rank-1,但如果参数类型需要是多态的,则它将变为rank-2 福尔 n n + 1 .

        5
  •  31
  •   C. A. McCann Ravikant Cherukuri    16 年前

    它们密密麻麻地塞满了假设,我读过离散数学、范畴论或抽象代数这周最新流行的分支(如果我再也没有读过“参考文件了解实施细节”,那就太早了。)

    forall 很明显是指 universal quantification existential 这也更有意义,不过如果有一个 exists 关键字。量化是有效的普遍性还是存在性取决于量词相对于变量在函数箭头的哪一边使用的位置,这有点混乱。

    所以,如果这没有帮助,或者如果你只是不喜欢符号逻辑,从函数式编程的角度来看,你可以认为类型变量只是(隐式的) 函数的参数。从这个意义上讲,接受类型参数的函数传统上都是使用大写lambda编写的,不管出于什么原因,我在这里写为 /\ .

    所以,考虑一下 id 功能:

    id :: forall a. a -> a
    id x = x
    

    我们可以将其重写为lambdas,将“类型参数”从类型签名中移出,并添加内联类型注释:

    id = /\a -> (\x -> x) :: a -> a
    

    const :

    const = /\a b -> (\x y -> x) :: a -> b -> a
    

    bar 函数可能是这样的:

    bar = /\a -> (\f -> ('t', True)) :: (a -> a) -> (Char, Bool)
    

    请注意,给定给 作为论据取决于 的类型参数。考虑一下,如果你有这样的东西:

    bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)
    

    在这里 bar2 将函数应用于某个类型的 Char 酒吧2 任何类型参数

    另一方面,这里是什么 foo 可能看起来像:

    foo = (\f -> (f Char 't', f Bool True))
    

    酒吧 , 实际上根本不接受任何类型参数!它需要一个函数 获取一个类型参数,然后将该函数应用于两个 不同的 类型。

    所以当你看到 福尔 类型签名的lambda表达式 . 就像普通的lambda一样 福尔 尽可能地向右扩展,直到括起来的圆括号,就像正则lambda中绑定的变量一样,类型变量由 福尔


    后脚本 :也许您会想——既然我们正在考虑函数接受类型参数,为什么我们不能对这些参数做一些比将它们放入类型签名更有趣的事情呢?答案是我们可以!

    将类型变量与标签放在一起并返回新类型的函数是 类型构造器 ,你可以这样写:

    Either = /\a b -> ...
    

    但是我们需要一个全新的符号,因为这样一个类型的书写方式 Either a b Either “这些参数”。

    另一方面,一个对其类型参数进行某种“模式匹配”的函数,为不同的类型返回不同的值,是一个 类型类的方法 . 我身体的轻微膨胀 /\

    fmap = /\ f a b -> case f of
        Maybe -> (\g x -> case x of
            Just y -> Just b g y
            Nothing -> Nothing b) :: (a -> b) -> Maybe a -> Maybe b
        [] -> (\g x -> case x of
            (y:ys) -> g y : fmap [] a b g ys 
            []     -> [] b) :: (a -> b) -> [a] -> [b]
    

    我个人认为我更喜欢Haskell的实际语法。。。

    类型族 函数依赖 --在前一种情况下,它甚至看起来已经非常像一个函数定义。

        6
  •  11
  •   Abhiroop Sarkar    8 年前

    我将试着解释一下 forall 在Haskell及其类型系统的背景下。

    Constraints Liberate, Liberties Constrain ". 这篇演讲充满了来自真实世界用例的例子,以及Scala中支持这一说法的例子,尽管它没有提到 福尔 . 我会尽力解释 下面是透视图。

                    CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN
    

    理解并相信这句话是非常重要的,所以我劝你看一下这篇演讲(至少是部分)。

    foo :: a -> a

    据说,给定这种类型的签名,只有一个函数可以满足这种类型,那就是 identity 功能或更广为人知的功能 id .

    在我学习Haskell的最初阶段,我总是想知道以下功能:

    foo 5 = 6
    
    foo True = False
    

    它们都满足上面的类型签名,那么为什么Haskell的人声称是这样呢 哪一个满足类型签名?

    这是因为有一个隐含的 福尔 隐藏在类型签名中。实际类型为:

    id :: forall a. a -> a
    

    约束解放,自由约束

    将其转换为类型系统,此语句将变为:

    类型级别的约束在术语级别变为自由

    类型级别的自由,变成术语级别的约束


    类型级别的约束。。

    所以对我们的类型签名施加一个约束

    foo :: (Num a) => a -> a
    

    给了我们自由或灵活性来写所有这些

    foo 5 = 6
    foo 4 = 2
    foo 7 = 9
    ...
    

    通过约束也可以观察到这一点 a

    那么现在这种类型的签名是什么: foo :: (Num a) => a -> a

    ∃a , st a -> a, ∀a ∈ Num
    

    这就是所谓的存在主义量化,也就是 存在 一些例子 一 当输入某种类型的函数时 一

    因此,我们可以看到添加一个约束(即 应该属于一组数字),将术语级别解放为具有多个可能的实现。


    接下来是第二个陈述,这个陈述实际上解释了 福尔

    类型级别的自由,变成术语级别的约束

    现在让我们在类型级别释放函数:

    foo :: forall a. a -> a
    

    ∀a , a -> a
    

    这意味着这个类型签名的实现应该是 a -> a 在任何情况下。

    所以现在这开始限制我们在学期水平。 我们不能再写了

    foo 5 = 7
    

    一 作为一个 Bool . 一 Char 或者 [Char]

    foo a = a
    

    身份 功能


    因此 福尔 是一个 liberty 在类型级别,其实际目的是 constrain 特定实现的术语级别。

        7
  •  10
  •   user370536 user370536    16 年前

    这个关键字有不同用法的原因是,它实际上至少在两个不同的类型系统扩展中使用:高等级类型和存在类型。

    最好只是分别阅读和理解这两件事,而不是试图解释为什么“forall”同时是这两种语言的适当语法。

        8
  •  4
  •   Community Mohan Dere    9 年前

    存在主义是如何存在的?

    用存在量化, forall s在 data 其中包含的值 属于 任何 合适的类型,而不是 是吗 必须 属于 合适的类型。 yachiru's answer

    解释原因 福尔 在里面 数据 (exists a. a) (伪Haskell)可以在 wikibooks's "Haskell/Existentially quantified types" .

    以下是简短的逐字摘要:

    data T = forall a. MkT a -- an existential datatype
    MkT :: forall a. a -> T -- the type of the existential constructor
    

    模式匹配/解构时 MkT x x ?

    foo (MkT x) = ... -- -- what is the type of x?
    

    可以是任何类型(如 福尔 ),所以它的类型是:

    x :: exists a. a -- (pseudo-Haskell)
    

    因此,以下是同构的:

    data T = forall a. MkT a -- an existential datatype
    data T = MkT (exists a. a) -- (pseudo-Haskell)
    

    福尔意味着福尔

    我对这一切的简单解释是 福尔 真正的意思是“为了所有人”。 福尔 上 定义 与功能

    福尔 是指 定义 值或函数的类型必须是多态的。

    价值 ,则表示该值必须对所有合适的 a

    如果所定义的事物是多态的 功能 ,则意味着函数必须对所有合适的 ,这不是限制性的,因为函数是多态的并不意味着参数是 应用 必须是多态的。也就是说,如果函数对所有 一 任何 一 可以是 应用

    如果 在函数参数的类型(即 Rank2Type )那就意味着 真正地 福尔 方法 定义 "and is chosen by the implementation of the function", as pointed out by Norman )

    因此,存在主义 数据 定义允许 是因为数据构造函数是多态的 功能 :

    MkT :: forall a. a -> T
    

    市场类型: a -> *

    一 价值 :

    valueT :: forall a. [a]
    

    价值类型: 一

    也就是说 定义 值必须是多态的。在这种情况下, valueT 可以定义为空列表 []

    [] :: [t]
    

    差异

    尽管 福尔 是一致的 ExistentialQuantification 和 RankNType 存在主义与现代主义有所不同 数据 构造函数可用于模式匹配。如文件所述 ghc user guide :

    推荐文章