Haskell中的“双重”函数组合

时间:2014-02-28 19:18:12

标签: haskell functional-programming abstract-data-type

我从编译器的建议中得到了一些看起来很奇怪的东西。

我创建了一个数据类型来表示二进制数,如下所示:

data Bin = Zero | One

我选择将多位二进制数表示为Bin类型的列表,如下所示:

myNum :: [Bin]
myNum = [One, Zero, One, One] -- represents the number 1011

当然,我想以更方便的方式显示我的二进制数字,所以我创建了一个Show实例来为我做这个:

instance Show Bin where
    show Zero = "0"
    show One  = "1"
    showList []     = showString ""
    showList (x:xs) = showString (show x) . showList xs

此方法有效,print myNum正确显示1011

我在Haskell仍然相当新,这是我第一次使用showList。但这对我来说很有意义。由于showList具有类型[a] -> ShowS(它本身是[a] -> (String -> String)的别名),我理解我通过使用函数组合“连接”列表的元素。但编译器建议我使用showList函数并重新定义它:

Warning: Use foldr
  Found:
    showList [] = showString ""
    showList (x : xs) = showString (show x) . showList xs
  Why not:
    showList xs = foldr ((.) . showString . show) (showString "") xs

而且,一旦我更换了它的建议,它就提出了进一步的建议:

Error: Eta reduce
  Found:
    showList xs = foldr ((.) . showString . show) (showString "") xs
  Why not:
    showList = foldr ((.) . showString . show) (showString "")

我理解Eta减少错误,因为通常更喜欢编写无点函数。但我在第一次转换时遇到了麻烦。我看到foldr的第二个参数是“基本情况”正确的身份,但我很难理解foldr的第一个参数中发生了什么。所以我有两个问题:

  1. 我怎么能重写((.) . showString . show)?例如,我知道(f . g) x可以重写为f (g x)
  2. 这在foldr
  3. 的背景下实际上做了什么

1 个答案:

答案 0 :(得分:1)

如何重写它如下,首先让我们将组合模式分解为单独的函数

 compose :: [a -> a] -> a -> a
 compose = foldr (.) id

您可以将其视为列表f : g : h : []并将[]替换为id: . f . g . h . idf . g . h instance Show Bin where showList = compose . map (showString . show)

接下来让我们用它来重写你的例子,让它更清晰

String -> String

现在这比你所拥有的更清晰,即使它在功能上是相同的。事实上,由于GHC可能会融合它,因此甚至可能最终编译成同样的东西(?)。我们正在将每个项目转换为ShowScompose,然后我们将它们全部组合起来。给 showList = appEndo . mconcat . map (Endo . showString . show) 一个名称和类型可以更容易地看到正在发生的事情。

我想这也可以写成

a -> a

这与上述相同,但依赖于mconcat形成一个幺半群而Endo概括了将一个幺半群组合成一个的概念。我们需要newtype Endo a = End {appEndo :: a -> a} 位,因为monoid实例实际上是为

定义的
{{1}}