【问题标题】:How to write this polyvariadic composition function in Haskell?如何在 Haskell 中编写这个多变量组合函数?
【发布时间】:2018-07-10 16:17:15
【问题描述】:

注意:这是another question的转贴,被作者删除。这是原始问题:


我在 Javascript 中有这个多变量 comp 函数,我想知道在 Haskell 中是否可以实现类似的实现。我最感兴趣的是comp的类型:

const comp = f => Object.assign(
  g => comp([g].concat(f)),
  {run: x => f.reduce((acc, h) => h(acc), x)}
);

const inc = n => n + 1;
const sqr = n => n * n;
const repeatStr = s => n => Array(n + 1).join(s);

comp(repeatStr("*")) (inc) (sqr).run(2); // "*****"

comp(repeatStr("*"))
  (inc)
  (inc)
  (inc)
  (inc)
  (inc).run(0); // "*****"

comp 构建了一个异构数组,在 Haskell 中通常没有类型。我猜这样一个可变参数函数的返回类型一定是多态的。然而,这个任务远远超出了我的 Haskell 知识。任何线索都会有所帮助。

上下文

我使用 Javascript 运行时类型检查器,以便我可以在 comp 中以类型安全的方式构建数组。它需要显式类型注释并且仅支持参数和 rank-2 多态性。

【问题讨论】:

  • 是的,就是我。很抱歉删除它!
  • 没关系。如果你愿意,你可以取消删除你的问题,我会在那里发布我的答案。之后,我可以删除这个问题。
  • @ftor 我想分享const comp = run => Object.assign(g => comp(x => g(run(x))), {run})
  • 对于其他感兴趣的人,这个功能是我在this answer制作的一个有趣的前向组合玩具的衍生产品
  • @naomik 我试图摆脱数组并将其替换为纯函数计算。我真的做到了。但我没有认出这种模式。它就在我面前!我只是有一种学习的紧迫感。

标签: javascript haskell functional-programming variadic-functions function-composition


【解决方案1】:

你是对的。您无法在 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 类型类,我们需要MultiParamTypeClassesFunctionalDependencies。要使用它,我们需要FlexibleInstances。因此,您需要一个复杂的 JavaScript 运行时类型检查器才能正确地检查 comp


编辑:正如 naomikDaniel 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 中创建异构函数列表,但它们没有可组合的额外约束。

【讨论】:

  • 周到而详细的回答,Aadit。非常好!
  • 你的方法比hackage.haskell.org/package/control-dotdotdot-0.1.0.1/docs/… 更优雅(即使你最后需要run)干得好!
  • 整个开发也可以通过newtype Comp a b = Comp { run :: a -> b }在没有 GADT 的情况下完成。 Comp 类型仅用于为Chain 实例解析提供基本情况。
  • @phadej Control.DotDotDot 包与我所做的不同。它定义了一个运算符,允许您将\x y z -> g (f x y z) 之类的函数编写为g ... f,而您通常将其编写为((g .) .) . f。见:What does (f .) . g mean in Haskell?
  • @DanielWagner 实际上,内部表示可以简单地是一个函数。为了忠实于 OP 的原始 JavaScript 代码,我只创建了一个 GADT。
猜你喜欢
  • 2016-08-13
  • 1970-01-01
  • 1970-01-01
  • 2020-10-25
  • 1970-01-01
  • 2011-03-28
  • 1970-01-01
  • 2011-01-10
  • 1970-01-01
相关资源
最近更新 更多