你是对的。您无法在 Haskell(1) 中构建可组合函数的异构列表。但是,您可以为可组合函数创建自己的列表数据类型,如下所示:
{-# LANGUAGE GADTs #-}
data Comp a b where
Id :: Comp a a
Comp :: Comp b c -> (a -> b) -> Comp a c
run :: Comp a b -> a -> b
run Id = id
run (Comp g f) = run g . f
Id 构造函数类似于[],Comp 构造函数类似于:,但参数翻转了。
接下来,我们使用varargs pattern 创建一个多变量函数。为此,我们定义了一个类型类:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Chain a b c | c -> a where
chain :: Comp a b -> c
请注意,我们的状态是Comp b c,我们的结果要么是Comp b c,要么是一个将另一个函数(a -> b)作为输入并将其与我们的状态组合以生成一个名为r的新Chain的函数。状态Comp a c。现在让我们为这些定义实例:
{-# LANGUAGE FlexibleInstances #-}
instance c ~ c' => Chain b c (Comp b c') where
chain = id
instance Chain a c r => Chain b c ((a -> b) -> r) where
chain g f = chain (Comp g f)
comp :: Chain b b c => c
comp = chain Id
comp 函数现在可以定义为chain Id(即以空列表Id 作为其状态的链)。我们终于可以像在 JavaScript 中那样使用这个 comp 函数了:
inc :: Int -> Int
inc = (+1)
sqr :: Int -> Int
sqr x = x * x
repeatStr :: String -> Int -> String
repeatStr s x = concat (replicate x s)
example1 :: String
example1 = comp (repeatStr "*") inc sqr `run` 2
example2 :: String
example2 = comp (repeatStr "*") inc inc inc inc inc `run` 0
把它们放在一起:
{-# LANGUAGE GADTs, MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances #-}
data Comp a b where
Id :: Comp a a
Comp :: Comp b c -> (a -> b) -> Comp a c
run :: Comp a b -> a -> b
run Id = id
run (Comp g f) = run g . f
class Chain a b c | c -> a where
chain :: Comp a b -> c
instance c ~ c' => Chain b c (Comp b c') where
chain = id
instance Chain a c r => Chain b c ((a -> b) -> r) where
chain g f = chain (Comp g f)
comp :: Chain b b c => c
comp = chain Id
inc :: Int -> Int
inc = (+1)
sqr :: Int -> Int
sqr x = x * x
repeatStr :: String -> Int -> String
repeatStr s x = concat (replicate x s)
example1 :: String
example1 = comp (repeatStr "*") inc sqr `run` 2
example2 :: String
example2 = comp (repeatStr "*") inc inc inc inc inc `run` 0
如您所见,comp 的类型是Chain b b c => c。要定义Chain 类型类,我们需要MultiParamTypeClasses 和FunctionalDependencies。要使用它,我们需要FlexibleInstances。因此,您需要一个复杂的 JavaScript 运行时类型检查器才能正确地检查 comp。
编辑:正如 naomik 和 Daniel Wagner 在 cmets 中指出的那样,您可以使用实际函数而不是可组合函数列表作为 comp 状态的内部表示.例如,在 JavaScript 中:
const comp = run => Object.assign(g => comp(x => g(run(x))), {run});
同样,在 Haskell 中:
{-# LANGUAGE GADTs, MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances #-}
newtype Comp a b = Comp { run :: a -> b }
class Chain a b c | c -> a where
chain :: Comp a b -> c
instance c ~ c' => Chain b c (Comp b c') where
chain = id
instance Chain a c r => Chain b c ((a -> b) -> r) where
chain g f = chain (Comp (run g . f))
comp :: Chain b b c => c
comp = chain (Comp id)
请注意,即使我们不再使用 GADT,我们仍然需要 GADTs 语言扩展,以便在 Chain 的第一个实例中使用等式约束 c ~ c'。此外,如您所见,run g . f 已从run 的定义移至Chain 的第二个实例。同样,id 已从 run 的定义移至 comp 的定义中。
(1) 您可以使用存在类型在 Haskell 中创建异构函数列表,但它们没有可组合的额外约束。