什么类型的问题有助于"更高通道的多态性"更好地解决?

met*_*eap 7 polymorphism haskell higher-kinded-types

当我阅读Haskell历史中的一些章节时,我遇到了:

但是,更高级别的多态性具有独立的效用:声明在更高种类上参数化的数据类型是完全可能的,有时非常有用,例如:

data ListFunctor f a = Nil | Cons a (f a)
Run Code Online (Sandbox Code Playgroud)

知道"基本"ADT我在这里有点困惑,我的"猜测"是parens中的部分表示"参数"/"动态" 一元数据构造函数 f?所以任何* -> *"可以接受"类型的数据构造函数a?我的想法是正确的还是我误解了语法?我知道我"只是在猜测",但我希望在这里能够获得一个关于这种能力的"平面程序员"直觉,一些需要(或从中受益匪浅)的示例场景;)大多数我可以想象(只是没有在什么精确的方式)这使得那些"小型嵌入式多功能可递送配置语言"-ADTs更加灵活,Haskell很高兴能够evals为... 编写和编写?

在GHCi中,:i ListFunctor基于以上给出:

type role ListFunctor representational nominal
data ListFunctor (f :: * -> *) a = Nil | Cons a (f a)
Run Code Online (Sandbox Code Playgroud)

所以这似乎是从更加清晰的data宣言中"推断出来的" .

chi*_*chi 14

是的,f可以是任何一元类型的构造函数.

例如,ListFunctor [] Int或者ListFunctor Maybe Char是很好的.

f 也可以是部分应用了(n-1)个参数的任何n元型构造函数.

例如,ListFunctor ((->) Bool) Int或者ListFunctor (Either ()) Char是很好的.

基本的kinding系统非常简单.如果F :: * -> * -> ... -> *,那么F期望类型参数.如果G :: (* -> *) -> *,那么G期望任何* -> *类型的东西,包括一元类型构造函数和部分应用程序,如上所示.等等.


更高级别可以很好地解决的问题是配置选项.假设我们有一个记录

data Opt = Opt 
   { opt1 :: Bool
   , opt2 :: String
   -- many other fields here
   }
Run Code Online (Sandbox Code Playgroud)

现在,配置设置可以在文件中找到和/或通过命令行和/或环境变量传递.在解析所有这些设置源的过程中,我们需要应对并非所有源都定义所有选项的事实.因此,我们需要一个更宽松的类型来表示配置设置的子集:

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
...
Run Code Online (Sandbox Code Playgroud)

这太可怕了,因为它复制了所有选项!我们很想去除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
...
Run Code Online (Sandbox Code Playgroud)

没有更多重复.在finalize配置设置之后,我们获得了始终存在设置的静态保证.我们现在可以使用访问器runIdentity来获取它们,而不是危险的fromJust.