简体   繁体   English

Haskell中的“双重”功能组合

[英]“Double” function composition in Haskell

I came across something that seems very strange to me, coming from the compiler's suggestions. 我从编译器的建议中发现了一些对我来说很奇怪的东西。

I made a data type to represent binary numbers as follows: 我做了一个数据类型来表示二进制数字,如下所示:

data Bin = Zero | One

I chose to represent multiple-digit binary numbers as lists of type Bin, like so: 我选择将多位二进制数表示为Bin类型的列表,如下所示:

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

Naturally, I want to display my binary numbers in a more convenient way, so I made an instance of Show to do this for me: 自然地,我想以一种更方便的方式显示我的二进制数,所以我创建了一个Show实例来为我做这件事:

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

This works, and print myNum correctly displays 1011 . print myNum ,并且print myNum正确显示1011

I'm still fairly new at Haskell, and this is my first time working with showList. 我在Haskell还是很新,这是我第一次使用showList。 But this makes sense to me. 但这对我来说很有意义。 Since showList has type [a] -> ShowS (which is itself an alias for [a] -> (String -> String) ), I understand that I "concatenate" the elements of a list by using function composition. 由于showList的类型为[a] -> ShowS (它本身是[a] -> (String -> String)的别名),因此我了解到我是通过使用函数组合来“串联”列表的元素的。 But the compiler suggested that I take the showList function and redefine it: 但是编译器建议我使用showList函数并重新定义它:

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

And, once I had replaced what it suggested, it made a further suggestion: 而且,一旦我替换了它的建议,它便提出了进一步的建议:

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

I understand the Eta reduce error, since it's usually preferable to write point-free functions. 我了解Eta减少错误,因为通常最好编写无点函数。 But I'm having trouble with the first conversion. 但是我在第一次转换时遇到了麻烦。 I see that the second argument to foldr is the "base case" right identity, but I'm having difficulty understanding what is going on in the first argument to foldr . 我看到foldr的第二个参数是“基本情况”正确的身份,但是我很难理解foldr的第一个参数中发生了什么。 So I have two questions: 所以我有两个问题:

  1. How could I rewrite ((.) . showString . show) ? 如何重写((.) . showString . show) For example, I know that (f . g) x can be re-written as f (gx) . 例如,我知道(f . g) x可以重写为f (gx)
  2. What does this actually do in the context of foldr ? 这实际上在文件foldr的上下文中有什么作用?

How about rewriting it as follows, first let's break out the pattern of composition into a separate function 如何重写它,如下所示,首先让我们将合成模式分解为一个单独的函数

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

You can visualize this as taking a list f : g : h : [] and replacing [] with id and : with . 您可以将其可视化为获取列表f : g : h : []并将[]替换为id: . , leaving you with f . g . h . id 给你留下f . g . h . id f . g . h . id f . g . h . id or f . g . h f . g . h . idf . g . h f . g . h f . g . h . f . g . h

Next let's use this to rewrite your example to make it a bit more legible 接下来,让我们用它来重写您的示例,使其更清晰易读

  instance Show Bin where
    showList = compose . map (showString . show)

Now this is a bit more legible than what you had even though it's functionally identical. 现在,尽管功能相同,但它比您现在的要清晰一些。 In fact this may end up even compiling as the same thing since GHC may fuse it(?). 实际上,由于GHC可能将其融合(?),因此甚至可能以相同的方式编译。 we're transforming each item into a String -> String or ShowS , and then we're composing them all. 我们将每个项目转换为ShowS String -> StringShowS ,然后将它们全部组成。 Giving a name and type to compose makes it much easier to see what's going on. 提供名称和类型来compose ,可以更轻松地了解正在发生的事情。

I suppose this could also be written 我想这也可以写成

 showList = appEndo . mconcat . map (Endo . showString . show)

This is identical to the above but relies on the fact that a -> a forms a monoid and mconcat generalizes the idea of combining a list of monoids to one. 这与上面的相同,但是依赖于以下事实: mconcat a -> a形成一个monoid,而mconcat推广了将一个monoid列表组合为一个的想法。 We need that Endo bit because the monoid instance is actually defined for 我们需要Endo位,因为实际上为

newtype Endo a = End {appEndo :: a -> a}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM