当我阅读History of Haskell中的某些部分时,我遇到了:
然而,更高级别的多态性具有独立的效用:声明在更高种类上参数化的数据类型是完全可能的,有时非常有用,例如:
data ListFunctor f a = Nil | Cons a (f a)
知道“基本”ADT我在这里有点疑惑,我的“猜测”是parens中的部分表示“参数化”/“动态”一元数据构造函数 f
?那么任何类型* -> *
的数据构造函数都可以“接受”类型a
?我的想法是正确的还是我误解了语法?我知道我“只是在猜测”,但我希望在这里能够获得这种能力的“平等程序员”直觉,一些需要(或从中受益匪浅)的示例场景;)大多数我可以想象(只是没有在什么确切的方式)这使得那些“小型嵌入式多功能可递送配置语言”-ADT更加灵活,Haskell很高兴能为{close}制定和编写evals
。
在GHCi中,上面的:i ListFunctor
给出了:
type role ListFunctor representational nominal
data ListFunctor (f :: * -> *) a = Nil | Cons a (f a)
所以这似乎是来自更加清晰的data
声明的“推断”。
答案 0 :(得分:14)
是的,f
可以是任何一元类型的构造函数。
例如,ListFunctor [] Int
或ListFunctor Maybe Char
是良好的。
f
也可以是部分应用了(n-1)个参数的任何n元型构造函数。
例如,ListFunctor ((->) Bool) Int
或ListFunctor (Either ()) Char
是良好的。
基本的绑定系统非常简单。如果F :: * -> * -> ... -> *
,则F
需要类型参数。如果G :: (* -> *) -> *
,则G
期望任何类型* -> *
包括一元类型构造函数和部分应用程序,如上所示。等等。
通过更高类型很好地解决的问题是配置选项。假设我们有一个记录
data Opt = Opt
{ opt1 :: Bool
, opt2 :: String
-- many other fields here
}
现在,配置设置可以在文件中找到和/或通过命令行和/或环境变量传递。在解析所有这些设置源的过程中,我们需要应对并非所有源都定义所有选项的事实。因此,我们需要一个更宽松的类型来表示配置设置的子集:
data TempOpt = TempOpt
{ tempOpt1 :: Maybe Bool
, tempOpt2 :: Maybe String
-- many other fields here
}
-- merge all options in one single configuration, or fail
finalize :: [TempOpt] -> Maybe Opt
...
这太可怕了,因为它复制了所有选项!我们很想删除Opt
类型,只使用较弱的TempOpt
来减少混乱。但是,通过这样做,我们需要在每次需要访问程序中的选项值时使用一些部分访问器,例如fromJust
,即使在初始配置处理部分之后也是如此。
我们可以采用更高级别的方式:
data FOpt f = FOpt
{ opt1 :: f Bool
, opt2 :: f String
-- many other fields here
}
type Opt = FOpt Identity
type TempOpt = FOpt Maybe
-- as before: merge all options in one single configuration, or fail
finalize :: [TempOpt] -> Maybe Opt
...
不再重复。在我们finalize
配置设置之后,我们获得了始终存在设置的静态保证。我们现在可以使用总计访问者runIdentity
来获取它们,而不是危险的fromJust
。