【问题标题】:Is it possible to define a higher-order "opposite" function on a case-by-case basis?是否可以根据具体情况定义更高阶的“相反”函数?
【发布时间】:2014-06-16 20:08:59
【问题描述】:

我是 Haskell 新手。我正在尝试在 Haskell 中创建一种迷你语言,如果可能的话,我希望有一个名为opp(“相反”的缩写)的高阶函数,它将许多熟悉的函数转换为它们明显的对立面。例如,opp succ 将是函数 predopp head 将是 last,等等。对于将一个函数转换为它的对立面意味着什么,我没有一些一般的定义:我只想挑选几个关键的例子并声明它们的对立面是什么。所以我想要一个几乎从未定义过的高度多态的函数。

困难似乎在于我想通过它们的名称而不是它们的本质来识别功能(可以这么说)。这种困难的一种表现是,如果我写了

opp succ = pred

然后 Haskell 将 succ 视为一个变量,因此给了我一个常量函数,它总是取值 pred。我真正想说的是,“如果您看到字符串opp succ,那么将其视为pred 的另一个名称。”但是在搜索了很长一段时间后,我不知道该怎么做(如果可能的话)。

总而言之,我想定义一个函数

opp :: (a -> b) -> (a -> b)

通过说类似的话

opp succ = pred

opp pred = succ

opp head = last

opp last = head

并在我喜欢的时候添加到此列表中。显然我不能那样做,但是有没有一些不可怕的方法可以达到同样的效果?

【问题讨论】:

  • 好的。但这个“对立面”的概念在当时似乎确实相当模糊。
  • 这就是重点。因为“相反”没有一个很好的定义,所以我想根据具体情况来定义它。
  • @user15553 那么为什么会有“对立面”呢?此时您可以拥有oppPred = succ,并且您不会失去与您在问题中指定的内容相关的表达能力。
  • 这是因为在迷你语言中能够将opp本身视为一个抽象概念很重要,因此在某种意义上succpred之间的关系是“与headlast 之间的关系相同”。这样做的一个(不是唯一的)优点是我可以使用它来构造其他函数,例如 oppPair f = (f, opp f),而不必以基本相同的方式构造许多类似的函数。

标签: haskell


【解决方案1】:

是的,你可以,但是你需要 RankNTypes 才能有一个好的实现。

{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE RankNTypes #-}
module Opposites where

class Opposite f where
  makeOpposite :: (a -> b) -> (a -> b) -> f a b


data FunctionAndOpposite a b = FunctionAndOpposite (a -> b) (a -> b)

instance Opposite (->) where
  makeOpposite = const

instance Opposite FunctionAndOpposite where
  makeOpposite = FunctionAndOpposite

opp :: FunctionAndOpposite a b -> a -> b
opp (FunctionAndOpposite _ f) = f

type a :<-> b = forall f. Opposite f => f a b

succ' :: Enum a => a :<-> a
succ' = makeOpposite succ pred

pred' :: Enum a => a :<-> a
pred' = makeOpposite pred succ

head' :: [a] :<-> a
head' = makeOpposite head last

last' :: [a] :<-> a
last' = makeOpposite last head

示例用法:

> head' [1,2,3]
1
> opp head' [1,2,3]
3

工作原理

首先,有Opposite 类。这只是描述了一个f a b,它可以由(a -&gt; b) 的两个函数(正常函数和相反函数)构成。

接下来,定义数据类型FunctionAndOpposite。这只是存储了两个函数。现在这两个标准函数,这是类Opposite 的实例。函数实例只是丢弃了相反的函数。

现在可以定义opp 函数。这只是从FunctionAndOpposite 中取出第二个函数。

最后,我们得到了将这一切结合在一起的那条线:

type a :<-> b = forall f. Opposite f => f a b

这定义的是一个类型同义词,它接受两个输入类型 a 和 b,然后返回一个类型 f a b,其中 f 可以是任何 Opposite(取决于需要什么)。

:&lt;-&gt; 只是使用TypeOperators 启用的类型的一个花哨名称)。

考虑代码head' [1,2,3]head' 的类型为 forall f. Opposite f =&gt; f [a] a。但是,由于它被用作函数应用程序(因为它后面直接有一个参数),所以f 必须是-&gt;。因此,使用了Opposite的函数实现,将第一个参数返回给makeOpposite,即head(太好了!)。

但是,假设opp head' [1,2,3] 被调用。 opp 的类型为 FunctionAndOpposite a b -&gt; a -&gt; b,因此 f 必须是 FunctionAndOpposite。因此,调用OppositeFunctionAndOpposite 实例,使用makeOpposite 的两个参数创建数据类型。 opp然后拉出第二个函数,就是last


顺便说一句,这是使用lens 库中用于Isomorphism 的类似技术完成的。同构基本上是一对互为逆的函数。例如(+2)(-2)reverse 和它本身。一般来说,这可能是一个更有用的概念,因为它允许您通过转换对数据类型运行操作。有关更多详细信息,请参阅Control.Lens.Iso,但请注意,要理解这一点,您需要了解镜头概念,这是一项非常艰巨的工作。

【讨论】:

  • 这看起来很棒——而且在我努力消化它时我希望能够理解的那种初级水平。非常感谢。
【解决方案2】:

如果我们将您的问题重新表述为这种形式,您可能会更清楚问题:

opp x | x == succ = pred
      | x == pred = succ

这将为您提供(a -&gt; b) 没有Eq 实例的错误,因为未定义函数的相等性,所以它不能具有该实例。

对此的解决方案是定义一个单独的数据类型,您可以对其进行模式匹配。

【讨论】:

  • 是的,我曾想过尝试这样做,但发现它不起作用,因为函数不属于相等类型。
  • 函数的相等性定义明确,只是不可计算。
  • ...正如 user15553 很容易指出的那样,没有 Eq 实例。
【解决方案3】:

也许可以使用某种基于堆上的闭包地址的哈希映射来识别函数。然后你可以创建这样一个逆表。不幸的是,你不会真正得到你想要的,因为函数只是值,因此它们是在编译器(或运行时)决定这样做时创建的。

例如即使你这么说

opp head = last

您可能(取决于编译器的实现)一无所获

opp (λx.head x) 

事实上,您在函数值上没有可靠的身份 - 因此我认为没有可用的方法来做您打算做的事情。

【讨论】:

    猜你喜欢
    • 2018-08-20
    • 2019-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-28
    • 2015-05-28
    • 1970-01-01
    相关资源
    最近更新 更多