【问题标题】:How can a function be "transparently augmented" in Haskell?如何在 Haskell 中“透明地增强”函数?
【发布时间】:2014-04-16 08:14:01
【问题描述】:

情况

我有函数 f,我想用函数 g 扩充它,从而生成名为 h 的函数。

定义

“增强”,在一般情况中,我的意思是:转换函数f 的输入(一个或多个参数)或输出(返回值)。

“增强”,在特定情况中,(特定于我目前的情况)我的意思是:仅转换函数f 的输出(返回值),同时保持所有参数不变。

通过“透明”,在“增强”的上下文中,(一般情况特定情况)我的意思是:耦合g的尽可能松散地实现f 的实现。

具体情况

在我目前的情况下,这是我需要做的:

h a b c = g $ f a b c

我有兴趣将其重写为如下内容:

h = g . f -- Doesn't type-check.

因为从hg 的角度来看,f 采用什么参数并不重要,它们只关心返回值,因此以任何方式提及参数都是紧密耦合的。例如,如果将来f 的参数计数发生变化,h 也将需要更改。

到目前为止

我在#haskell IRC 频道上询问了 lambdabot:@pl h a b c = g $ f a b c,我得到了回复:

h = ((g .) .) . f

这仍然不够好,因为(.) 的数量取决于f 的参数数量。

一般情况

我在这个方向上没有做太多研究,但#haskell 上的erisco 将我指向http://matt.immute.net/content/pointless-fun,这向我暗示一般情况的解决方案是可能的。

到目前为止

使用上篇文章中 Luke Palmer 定义的函数,这似乎等同于我们目前所讨论的内容:

h = f $. id ~> id ~> id ~> g

但是,如果我们想转换 f 的返回值,这个方法似乎也很遗憾地依赖于 f 的参数数量——就像以前的方法一样。

工作示例

例如,在 JavaScript 中,可以像这样实现透明增强:

function h () { return g(f.apply(this, arguments)) }

问题

如何在 Haskell 中“透明地增强”函数?

我主要对特定情况感兴趣,但如果知道如何处理一般情况也很好。

【问题讨论】:

  • 假设有可能,那么h的类型应该是什么?这不应该取决于f的类型吗?
  • @LeeDuhem 是的,h 的类型将基于 f,除了返回值,它将是@987654349 返回值的类型@.
  • @LeeDuhem 像这样:f :: ... -> a {- we don't care/don't know what is in '...' -}g :: a -> bh :: ... -> b {- '...' is the same as for f -}
  • h 需要一个类型,以便 ... 成为某种东西。虽然你可以用类型变量来表达“我不关心具体类型是什么”的意思,但据我所知,在 Haskell 中没有办法表达我不关心有多少参数。跨度>
  • 你在一个多参数函数的类型中明确了隐式括号,然后尝试应用你的省略号,你会明白为什么它这么难。 (省略号不捕获右括号。)例如a -> b -> c -> d ~ a -> (b -> (c -> d)).

标签: haskell


【解决方案1】:

你可以做到这一点,但由于没有办法为所有不是函数的事物指定行为,因此你需要为所有其他事物提供大量琐碎的实例你关心的类型。

{-# LANGUAGE TypeFamilies, DefaultSignatures #-}

class Augment a where
  type Result a
  type Result a = a

  type Augmented a r
  type Augmented a r = r

  augment :: (Result a -> r) -> a -> Augmented a r

  default augment :: (a -> r) -> a -> r
  augment g x = g x

instance Augment b => Augment (a -> b) where
  type Result (a -> b) = Result b
  type Augmented (a -> b) r = a -> Augmented b r

  augment g f x = augment g (f x) 

instance Augment Bool
instance Augment Char
instance Augment Integer
instance Augment [a]

-- and so on for every result type of every function you want to augment...

例子:

> let g n x ys = replicate n x ++ ys
> g 2 'a' "bc"
"aabc"
> let g' = augment length g
> g' 2 'a' "bc"
4
> :t g
g :: Int -> a -> [a] -> [a]
> :t g'
g' :: Int -> a -> [a] -> Int

【讨论】:

  • @Wizek:好主意!完成。
  • 能否使用封闭类型族在 GHC 7.8 上进一步清理?
  • 我有一个与@Ed'ka 类似的问题:如果我们要将类和实例放入它自己的模块中并且只导出扩充函数(和扩充类? ),导入它并在其他模块中使用它是否安全?安全,我的意思是:它是可靠的还是会有奇怪和意想不到的极端情况?我喜欢你不依赖 IncoherentInstances 杂注,但当然有必须指定很多实例的缺点。 (我们可能会按照 Data.Default 的方式来做......)很长的问题:这是一个泄漏的抽象吗?
  • @Wizek:它是可靠的,但如果您要扩充的函数的返回类型是类型变量,它可能无法按照您希望的方式工作,因为如果该类型变量被实例化对于一个函数类型,它会想要继续扩充 that。基本上,增强不会停止,直到绝对确定不再有争论为止。这是这样做与使用IncoherentInstances 之间的最大区别,因为在某些情况下,通过选择基本案例实例而不是递归函数实例,可能会提前停止。
  • @Wizek:例如,augment (\f x -> f x x) (!!) [(||), (&&)] 0 True 将不起作用,因为它会尝试继续扩充列表中的函数,但您可以执行augment not (!!) [(||), (&&)] 0 True False。 “组合过去的两个参数”函数将在第一种情况下工作,而“组合过去的四个参数”在第二种情况下,但augment 永远不能组合超过所有这些,即使类型可以告诉你这没有意义。
【解决方案2】:

嗯,从技术上讲,只需足够的IncoherentInstances,您几乎可以做任何事情:

{-# LANGUAGE MultiParamTypeClasses, TypeFamilies,
  FlexibleInstances, UndecidableInstances, IncoherentInstances #-}

class Augment a b f h where
   augment :: (a -> b) -> f -> h

instance (a ~ c, h ~ b) => Augment a b c h where
   augment = ($)

instance (Augment a b d h', h ~ (c -> h')) => Augment a b (c -> d) h where
   augment g f = augment g . f

-- Usage
t1 = augment not not
r1 = t1 True

t2 = augment (+1) (+)
r2 = t2 2 3

t3 = augment (+1) foldr
r3 = t3 (+) 0 [2,3]

【讨论】:

  • 如果我们将类和两个实例放到自己的模块中,只导出augment函数,那么导入它并在其他模块中使用它是否安全?安全,我的意思是:它是可靠的还是会有奇怪和意想不到的极端情况?我非常喜欢这种方式,您不必像@hammar 的回答那样列出您必须列出的类型,但我对依赖名称不连贯的语言编译指示有点谨慎。 简短的长问题:这是一个泄漏的抽象吗?
  • @Wizek 就我个人而言,如果只导出 augment 函数,我不明白为什么它会“泄漏”。
  • 正如@monocell 所指出的那样,使用OverlappingInstances pragma 是否足够安全一些?
【解决方案3】:

问题是a -> b -> c 之类的真正返回值不是 c,但b -> c。你想要什么需要某种测试来告诉你类型是否不是 一个函数类型。您可以枚举您感兴趣的类型,但事实并非如此 好的。我认为HList 以某种方式解决了这个问题,看看paper。我设法理解了一些重叠实例的解决方案,但其余的恐怕有点超出我的想象。

【讨论】:

  • 难道不能以某种方式判断一个函数是否是一个函数,如果不是在编译时,也许是在运行时?
  • 我相信在这种情况下等待不会给你带来任何好处。不过,可以让类型检查器对此进行检查,请查看 Ed'ka、hammar 或我链接到的论文的答案。这并不容易或很好。
  • 实现不必简单或漂亮。如果它很丑,但可以通过将其放入自己的模块中来完美地抽象(嗯,泄漏抽象的反义词是什么?),那么我可以从那里导入(可靠的)增强函数。
  • 我认为只要您不定义新实例就应该是安全的。另外我相信 OverlappingInstances 就足够了,你可能不需要 Incoherent。
【解决方案4】:

JavaScript 有效,因为它的参数是一个序列或列表,所以实际上只有一个参数。从这个意义上说,它与带有表示参数集合的元组的函数的柯里化版本相同。

在强类型语言中,您需要更多信息才能“透明”地为函数类型执行此操作 - 例如,依赖类型可以表达这个想法,但要求函数是特定类型,而不是任意函数类型.

我想我在 Haskell 中看到了一种解决方法,它也可以做到这一点,但同样,它只适用于特定类型,它捕获函数的数量,而不是 any 函数。 p>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-25
    • 2012-09-20
    相关资源
    最近更新 更多