当我阅读 History of Haskell 中的一些部分时,我发现:
However, higher-kinded polymorphism has independent utility: it is entirely possible, and occasionally very useful, to declare data types parameterised over higher kinds, such as:
data ListFunctor f a = Nil | Cons a (f a)
了解“基本”ADT,我在这里有点困惑,我的“猜测”是括号中的部分建议“参数”/“动态”一元数据构造函数 f
?那么任何类型为 * -> *
的数据构造函数“可以接受”类型 a
吗?我的想法是正确的还是我误解了语法?我知道我“只是猜测”,但我希望在这里获得对此功能的“外行程序员”直觉,一些示例场景需要(或从中受益匪浅);)大多数情况下我可以想象(只是不是在什么情况下)确切的方式)这使得那些“小型嵌入式通用可递归配置语言”-ADT 具有更大的灵 active ,Haskell 非常乐意为这些“小型嵌入式通用可递归配置语言”制定和编写评估
......关闭?
在 GHCi 中,上面的 :i ListFunctor
给出:
type role ListFunctor representational nominal
data ListFunctor (f :: * -> *) a = Nil | Cons a (f a)
所以这似乎是从更清晰的 data
声明中“推断”出来的。
最佳答案
是的,f
可以是任何一元类型构造函数。
例如 ListFunctor [] Int
或 ListFunctor Maybe Char
都是很好的。
f
也可以是任何 n 元类型构造函数,部分应用了 (n-1) 个参数。
例如,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
...
不再有重复。在我们最终确定
配置设置后,我们得到了设置始终存在的静态保证。我们现在可以使用total访问器runIdentity
来获取它们,而不是危险的fromJust
。
关于haskell - 哪些类型的问题可以帮助 "higher-kinded polymorphism"更好地解决?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42370444/