什么是Rank2Types的目的是什么?(What is the purpose of Rank2T

2019-06-17 19:29发布

我不是在Haskell真正精通,所以这可能是一个非常简单的问题。

什么语言限制Rank2Types解决? 不要在Haskell功能已经支持多态参数呢?

Answer 1:

不要在Haskell功能已经支持多态参数呢?

他们这样做,但只有排名1。这意味着,虽然您可以编写一个函数,接受不同类型的参数没有这个扩展,你可以不写使用其作为不同类型的相同的调用参数的函数。

例如,下面的功能不能在没有这种扩展,因为输入g与在定义不同参数类型使用f

f g = g 1 + g "lala"

请注意,这是完全可能通过一个多态函数作为参数传递给另一个函数。 因此,像map id ["a","b","c"]是完全合法的。 但功能可能只把它作为单态。 在这个例子中map使用id ,如果它有输入String -> String 。 当然,你也可以通过给定类型的,而不是一个简单的单态函数id 。 没有rank2types没有办法的功能,要求其参数必须是多态的功能,因此也没有办法把它作为一个多态函数。



Answer 2:

这是很难理解较高等级的多态性,除非你学习系统F直接,因为Haskell是设计成隐藏,从你在简单的利益的细节。

但基本上,粗糙的想法是多态的类型不真的有a -> b形式,它们在Haskell做; 在现实中,他们这个样子,总是有明确的量词:

id :: ∀a.a → a
id = Λt.λx:t.x

如果你不知道“∀”符号,它读作“所有”; ∀x.dog(x)的意思是“对于所有x,x为狗”。 “Λ”是资本拉姆达,用于提取在类型参数; 什么第二行说的是,ID是一个函数,需要一个类型t ,然后返回由一款类型参数化的功能。

你看,在F系统,你不能只申请一个这样的功能id的值马上; 首先你需要将Λ功能适用于类型,以获得您应用于值的λ-功能。 因此,例如:

(Λt.λx:t.x) Int 5 = (λx:Int.x) 5
                  = 5

标准哈斯克尔(即哈斯克尔98和2010年),由不具有这些类型的量词,资本lambda表达式和类型应用程序的简化了这个要求,但幕后GHC把他们当它分析了编译程序。 (这是所有编译时的东西,我相信,没有运行时开销。)

但是,Haskell的这种自动处理意味着,它假定“∀”永远不会出现在函数的左手分支(“→”)类型。 Rank2TypesRankNTypes关闭这些限制,并允许您覆盖在哪里插入Haskell的默认规则forall

你为什么想做这个? 因为完整的,不受限制的系统F是海拉强大,它可以做很多很酷的东西。 例如,键入隐藏和模块化可使用较高等级类型来实现。 采取例如下列秩1型(现场设置)的一个普通的旧功能:

f :: ∀r.∀a.((a → r) → a → r) → r

要使用f ,调用者首先要选用什么类型ra ,然后提供结果类型的参数。 所以,你可以挑r = Inta = String

f Int String :: ((String → Int) → String → Int) → Int

但现在比较,为下面的上位类型:

f' :: ∀r.(∀a.(a → r) → a → r) → r

请问这个类型的函数工作? 好了,使用它,首先你指定要使用哪种类型的r 假设我们挑Int

f' Int :: (∀a.(a → Int) → a → Int) → Int

但现在的∀a是功能箭头里面 ,所以你不能选择使用何种类型的a ; 你必须申请f' Int到相应类型的Λ功能。 这意味着, 执行f'得到挑来使用什么类型a ,不来电f' 。 如果没有更高级别的类型,相反,主叫方总是挑选的类型。

这是什么用呢? 嗯,其实很多事情,但一个想法是,你可以用它来模拟之类的面向对象编程,其中“对象”有一些方法上的隐藏数据工作捆绑在一起,一些隐藏的数据。 因此,例如,有两个方法:一个返回一个对象Int ,而另一个返回一个String ,可以在本类型实现:

myObject :: ∀r.(∀a.(a → Int, a -> String) → a → r) → r

这是如何运作的? 该目的是这样实现的,其具有隐藏式的一些内部数据的功能a 。 实际使用的对象,它的客户传递的对象将两种方法称之为“回调”功能。 例如:

myObject String (Λa. λ(length, name):(a → Int, a → String). λobjData:a. name objData)

我们在这里,基本上,调用该对象的第二种方法,其类型是一个a → String一个未知的a 。 好了,不知道的myObject的客户; 但这些客户知道,从签名,他们将能够任意的两个函数适用于它,并得到任何一个IntString

对于一个实际的Haskell例如,下面是我自学的,我写的代码RankNTypes 。 这实现了一个名为类型ShowBox这与它一起捆绑了一些隐藏的类型的值Show类的实例。 需要注意的是在底部的例子,我做的名单ShowBox它的第一个元素从一些已经作出,从字符串第二。 由于该类型是使用较高等级类型隐藏,这并不违反类型检查。

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ImpredicativeTypes #-}

type ShowBox = forall b. (forall a. Show a => a -> b) -> b

mkShowBox :: Show a => a -> ShowBox
mkShowBox x = \k -> k x

-- | This is the key function for using a 'ShowBox'.  You pass in
-- a function @k@ that will be applied to the contents of the 
-- ShowBox.  But you don't pick the type of @k@'s argument--the 
-- ShowBox does.  However, it's restricted to picking a type that
-- implements @Show@, so you know that whatever type it picks, you
-- can use the 'show' function.
runShowBox :: forall b. (forall a. Show a => a -> b) -> ShowBox -> b
-- Expanded type:
--
--     runShowBox 
--         :: forall b. (forall a. Show a => a -> b) 
--                   -> (forall b. (forall a. Show a => a -> b) -> b)
--                   -> b
--
runShowBox k box = box k


example :: [ShowBox] 
-- example :: [ShowBox] expands to this:
--
--     example :: [forall b. (forall a. Show a => a -> b) -> b]
--
-- Without the annotation the compiler infers the following, which
-- breaks in the definition of 'result' below:
--
--     example :: forall b. [(forall a. Show a => a -> b) -> b]
--
example = [mkShowBox 5, mkShowBox "foo"]

result :: [String]
result = map (runShowBox show) example

PS:任何人读这是谁不知道怎么来ExistentialTypes在GHC使用forall ,我相信原因是因为它使用这种幕后技术。



Answer 3:

路易斯·卡西利亚斯的答案给出了很多的约2种意思排名伟大的信息,但我就扩大一次,他没有涉及。 需要一个参数是多态的不只是允许它与多种类型的使用; 这也限制了什么功能可以用它的参数(S),以及它如何产生的结果做。 也就是说,它给调用者缺乏灵活性。 你为什么想这么做? 我会用一个简单的例子开始:

假设我们有一个数据类型

data Country = BigEnemy | MediumEnemy | PunyEnemy | TradePartner | Ally | BestAlly

我们要编写一个函数

f g = launchMissilesAt $ g [BigEnemy, MediumEnemy, PunyEnemy]

这需要一个本应选择它给列表中的元素之一,并返回一个函数IO在这一目标的行动发射导弹。 我们可以给f简单类型:

f :: ([Country] -> Country) -> IO ()

问题是,我们可能会不小心运行

f (\_ -> BestAlly)

然后我们就麻烦大了! 给予f秩1多态型

f :: ([a] -> a) -> IO ()

没有在所有帮助,因为我们选择的类型a ,当我们要求f ,我们只是它专注于Country ,并使用我们的恶意\_ -> BestAlly一次。 的解决方案是使用一个秩2类型:

f :: (forall a . [a] -> a) -> IO ()

现在,我们通过在功能要求是多态的,所以\_ -> BestAlly将不会打字检查! 事实上, 没有任何功能它被赋予将进行类型检查列表中未返回一个元素(虽然有些函数进入无限循环或产生错误,因此不会再回来将这样做)。

以上是做作,当然,但这种技术的变化,关键是使ST单子安全。



Answer 4:

较高等级类型不异国情调的其他答案都做出来。 信不信由你,许多面向对象的语言(包括Java和C#!)对它们进行展示。 (当然,没有人在这些社区由可怕的名曰“上位的类型”知道他们。)

我想给这个例子是一个教科书实现Visitor模式,这是我用所有的时间在我的日常工作。 这个答案是不打算作一介绍访问者模式; 知识是容易 获得 的其他地方 。

在这个昏庸虚人力资源应用程序,我们希望员工谁可能是长期的全职工作人员或临时合同工作。 我的Visitor模式(实际上也是其中一个是有关的优选变体RankNTypes )parameterises访问者的返回类型。

interface IEmployeeVisitor<T>
{
    T Visit(PermanentEmployee e);
    T Visit(Contractor c);
}

class XmlVisitor : IEmployeeVisitor<string> { /* ... */ }
class PaymentCalculator : IEmployeeVisitor<int> { /* ... */ }

问题的关键是,一些具有不同的返回类型的游客都可以在相同的数据进行操作。 这意味着IEmployee必须表示没有意见,以什么T应该是。

interface IEmployee
{
    T Accept<T>(IEmployeeVisitor<T> v);
}
class PermanentEmployee : IEmployee
{
    // ...
    public T Accept<T>(IEmployeeVisitor<T> v)
    {
        return v.Visit(this);
    }
}
class Contractor : IEmployee
{
    // ...
    public T Accept<T>(IEmployeeVisitor<T> v)
    {
        return v.Visit(this);
    }
}

我想提请你注意类型。 观察IEmployeeVisitor普遍量化它的返回类型,而IEmployee量化它其内部Accept的方法-也就是说,在更高的排名。 从C#clunkily翻译哈斯克尔:

data IEmployeeVisitor r = IEmployeeVisitor {
    visitPermanent :: PermanentEmployee -> r,
    visitContractor :: Contractor -> r
}

newtype IEmployee = IEmployee {
    accept :: forall r. IEmployeeVisitor r -> r
}

所以你有它。 较高等级类型显示在C#中,当你写一个包含泛型方法的类型。



Answer 5:

从斯坦福大学布赖恩奥沙利文的哈斯克尔课程的PPT帮助我理解Rank2Types



Answer 6:

对于那些熟悉面向对象语言中,较高等级的功能仅仅是一个通用的函数,预计作为其参数的另一个通用功能。

例如,在打字稿你可以写:

type WithId<T> = T & { id: number }
type Identifier = <T>(obj: T) => WithId<T>
type Identify = <TObj>(obj: TObj, f: Identifier) => WithId<TObj>

见通用功能型如何Identify需求类型的通用功能Identifier ? 这使得Identify一个较高等级的功能。



文章来源: What is the purpose of Rank2Types?