我不是在Haskell真正精通,所以这可能是一个非常简单的问题。
什么语言限制Rank2Types解决? 不要在Haskell功能已经支持多态参数呢?
我不是在Haskell真正精通,所以这可能是一个非常简单的问题。
什么语言限制Rank2Types解决? 不要在Haskell功能已经支持多态参数呢?
不要在Haskell功能已经支持多态参数呢?
他们这样做,但只有排名1。这意味着,虽然您可以编写一个函数,接受不同类型的参数没有这个扩展,你可以不写使用其作为不同类型的相同的调用参数的函数。
例如,下面的功能不能在没有这种扩展,因为输入g
与在定义不同参数类型使用f
:
f g = g 1 + g "lala"
请注意,这是完全可能通过一个多态函数作为参数传递给另一个函数。 因此,像map id ["a","b","c"]
是完全合法的。 但功能可能只把它作为单态。 在这个例子中map
使用id
,如果它有输入String -> String
。 当然,你也可以通过给定类型的,而不是一个简单的单态函数id
。 没有rank2types没有办法的功能,要求其参数必须是多态的功能,因此也没有办法把它作为一个多态函数。
这是很难理解较高等级的多态性,除非你学习系统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的这种自动处理意味着,它假定“∀”永远不会出现在函数的左手分支(“→”)类型。 Rank2Types
和RankNTypes
关闭这些限制,并允许您覆盖在哪里插入Haskell的默认规则forall
。
你为什么想做这个? 因为完整的,不受限制的系统F是海拉强大,它可以做很多很酷的东西。 例如,键入隐藏和模块化可使用较高等级类型来实现。 采取例如下列秩1型(现场设置)的一个普通的旧功能:
f :: ∀r.∀a.((a → r) → a → r) → r
要使用f
,调用者首先要选用什么类型r
和a
,然后提供结果类型的参数。 所以,你可以挑r = Int
和a = 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
的客户; 但这些客户知道,从签名,他们将能够任意的两个函数适用于它,并得到任何一个Int
或String
。
对于一个实际的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
,我相信原因是因为它使用这种幕后技术。
路易斯·卡西利亚斯的答案给出了很多的约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
单子安全。
较高等级类型不异国情调的其他答案都做出来。 信不信由你,许多面向对象的语言(包括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#中,当你写一个包含泛型方法的类型。
从斯坦福大学布赖恩奥沙利文的哈斯克尔课程的PPT帮助我理解Rank2Types
。
对于那些熟悉面向对象语言中,较高等级的功能仅仅是一个通用的函数,预计作为其参数的另一个通用功能。
例如,在打字稿你可以写:
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
一个较高等级的功能。