【问题标题】:What's the point of 'const' in the Haskell Prelude?Haskell Prelude 中的“const”有什么意义?
【发布时间】:2011-09-13 13:17:51
【问题描述】:

翻阅Haskell Prelude,我see a functionconst

const x _ = x

我似乎找不到与此功能相关的任何内容。

有什么意义?谁能举例说明这个函数在哪里可以使用?

【问题讨论】:

  • 一个例子:backgroundColor :: Text -> Color 是我的backgroundColor = const White

标签: haskell


【解决方案1】:

当您不需要它们的所有灵活性时,它对于传递给高阶函数很有用。例如,一元序列运算符>> 可以根据一元绑定运算符定义为

x >> y = x >>= const y

它比使用 lambda 更简洁

x >> y = x >>= \_ -> y

你甚至可以免费使用它

(>>) = (. const) . (>>=)

虽然在这种情况下我并不特别推荐。

【讨论】:

  • +1。它在使用解析器组合器时也经常出现。
  • 啊,所以它更像是一个“函数生成器”——我将它与一个参数一起使用,它给了我一个总是返回一个常量值的函数(带一个参数)。所以map (const 42) [1..5] 的结果是[42, 42, 42, 42, 42]
  • stusmith:你明白了。 const 对于应用于单个参数以产生需要的函数(例如传递给 map)很有用。
  • @stusmith:你可以用一些有趣的方式使用它:head = foldr const (error "Prelude.head: empty list")
【解决方案2】:

添加到 hammar 出色的直接答案:像 constid 这样的不起眼的函数作为高阶函数非常有用,因为它们在SKI combinator calculus 中是基本的。

并不是说我认为 haskell 的前奏函数是有意识地模仿那个正式的系统或任何东西。只是在 haskell 中创建丰富的抽象非常容易,因此您经常会看到这些类型的理论东西在实践中出现。

无耻的插件,但我在博客上写过 (->) 的 Applicative 实例实际上是 SK 组合器 here,如果你喜欢这样的话。

【讨论】:

  • 好吧,SKI 组合器确实影响了 Prelude。我记得我和 Joe Fasel 争论过是否应该包含 S 组合子。
  • 顺便说一句,((->) e) 也是阅读器 monad——Reader 等只是 newtype 包装器——然后ask 函数就是 id,所以这就是I 组合器也是如此。如果您查看 Haskell Curry 的原始 BCKW 基础,BKW 分别是 fmapreturnjoin
  • 答案中的博客链接已失效。它现在应该指向这里:brandon.si/code/…
【解决方案3】:

我似乎找不到与此功能相关的任何内容。

许多其他答案讨论了const 的相对深奥(至少对新手而言)应用。这是一个简单的例子:您可以使用const 来摆脱一个接受两个参数的 lambda,丢弃第一个参数,但对第二个参数做了一些有趣的事情。

例如,length 的以下(低效但有启发性)实现,

length' = foldr (\_ acc -> 1 + acc) 0

可以改写为

length' = foldr (const (1+)) 0

这也许更优雅。

表达式const (1+) 在语义上确实等同于\_ acc -> 1 + acc,因为它接受一个参数,将其丢弃,然后返回部分 (1+)

【讨论】:

  • 我花了 5 分钟才明白这是如何工作的 :)
  • foldr 对于新手来说也不是那么容易?
  • 耐心点。这需要时间:)
【解决方案4】:

使用const 的一个简单示例是Data.Functor.(<$)。有了这个函数,你可以说:我这里有一个函子,里面有一些无聊的东西,但我想在里面有其他有趣的东西,而不改变函子的形状。例如

import Data.Functor

42 <$ Just "boring"
--> Just 42

42 <$ Nothing
--> Nothing

"cool" <$ ["nonsense","stupid","uninteresting"]
--> ["cool","cool","cool"]

定义是:

(<$) :: a -> f b -> f a
(<$) =  fmap . const

或者写得不是毫无意义:

cool <$ uncool =  fmap (const cool) uncool

您会看到这里如何使用const 来“忘记”输入。

【讨论】:

    【解决方案5】:

    另一个用途是实现具有不应评估的虚拟参数的类成员函数(用于解决模棱两可的类型)。可能在 Data.bits 中的示例:

    instance Bits Int where
      isSigned = const True
      bitSize  = const wordSize
      ...
    

    通过使用 const,我们明确表示我们正在定义常量值。

    我个人不喜欢使用虚拟参数,但如果在类中使用它们,那么这是编写实例的一种相当不错的方式。

    【讨论】:

    • 代理参数确实要好得多,并且在针对最近的 GHC 时,类型应用程序可以巧妙地解决问题。
    【解决方案6】:

    假设您要创建一个Nothings 的列表,该列表等于字符串的长度。由于const 返回它的第一个参数,不管是第二个,你都可以这样做:

    listOfNothings :: String -> [Maybe Char]
    listOfNothings = (map . const) Nothing
    

    或者,更明确地说:

    listOfNothing st = map (const Nothing) st
    

    【讨论】:

      【解决方案7】:

      const 可能只是您正在寻找的与其他功能结合使用的实现。这是我发现的一个例子。

      假设我们想将一个 2 元组结构重写为另一个 2 元组结构。我可以这样表达:

      ((a,b),(c,d)) ⇒ (a,(c,(5,a)))
      

      我可以通过模式匹配给出一个直截了当的定义:

      f ((a,b),(c,d)) = (a,(c,(5,a)))
      

      如果我想为这类重写提供一个毫无意义(默认)的解决方案怎么办?稍后思考和摆弄,答案是我们可以用(&amp;&amp;&amp;), const, (.), fst, snd表达任何重写。请注意,(&amp;&amp;&amp;) 来自 Control.Arrow

      使用这些函数的例子的解决方案是:

      (fst.fst &&& (fst.snd &&& (const 5 &&& fst.fst)))
      

      注意与(a,(c,(5,a))) 的相似之处。如果我们将&amp;&amp;&amp; 替换为, 会怎样?然后是这样的:

      (fst.fst, (fst.snd, (const 5, fst.fst)))
      

      注意a 是第一个元素的第一个元素,这就是fst.fst 的项目。注意c 是第二个元素的第一个元素,这就是fst.snd 的项目。也就是说,变量成为其来源的路径。

      const 允许我们引入常量。有趣的是,名字与含义是如何一致的!

      然后我用 Applicative 概括了这个想法,这样你就可以用毫无意义的风格编写任何函数(只要你有可用的案例分析作为函数,例如 maybeeitherbool)。同样,const 扮演着引入常量的角色。您可以在 Data.Function.Tacit 包中看到这项工作。

      当您从抽象的目标开始,然后朝着实现的方向努力时,您可能会对答案感到惊讶。也就是说,任何一种功能都可能像机器中的任何一个齿轮一样神秘。但是,如果您将整个机器拉回来,您可以了解需要该齿轮的上下文。

      【讨论】:

        【解决方案8】:

        假设您要轮换列表。这是在 Haskell 中的惯用方式:

        rotate :: Int -> [a] -> [a] rotate _ [] = [] rotate n xs = zipWith const (drop n (cycle xs)) xs

        此函数使用函数const 压缩两个数组,第一个是无限循环数组,第二个是您开始使用的数组。

        const作为边界检查,使用原始数组终止循环数组。

        见:Rotate a list in Haskell

        【讨论】:

          【解决方案9】:

          我似乎找不到与此功能相关的任何内容。

          假设您想生成给定列表的所有子序列。

          对于每个列表元素,在给定点,您可以选择 True(将其包含在当前子序列中)或 False(不包含它)。这可以使用filterM 函数来完成。

          像这样:

           λ> import Control.Monad
           λ> :t filterM
           filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a]
           λ> 
          

          例如,我们想要[1..4] 的所有子序列。

           λ> filterM  (const [True, False])  [1..4]
           [[1,2,3,4],[1,2,3],[1,2,4],[1,2],[1,3,4],[1,3],[1,4],[1],[2,3,4],[2,3],[2,4],[2],[3,4],[3],[4],[]]
           λ> 
          

          【讨论】:

            猜你喜欢
            • 2011-06-30
            • 2011-07-01
            • 2011-12-04
            • 2012-08-07
            • 2016-12-09
            • 1970-01-01
            • 2023-03-15
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多