【问题标题】:Learning Haskell - How to simplify expressions?学习 Haskell - 如何简化表达式?
【发布时间】:2014-08-30 04:55:15
【问题描述】:

有什么办法可以减轻表达简化的痛苦吗?

例如,给定这个表达式:

(+) <$> a <*> b $ 1

我很想看到一个可以解释其含义的工具。对于初学者(在源代码中找到正确的实例函数定义,检查运算符优先级)来说,通过所有步骤简化表达式是相当费力的:

fmap (+) a <*> b $ 1

definitionData.Functor

(.) (+) a <*> b $ 1  

Control.Monad.Instances 中查看fmap instance Functor ((-&gt;) r)

等等。

编辑:为了澄清,我正在寻找一种使用实际函数定义重写表达式的方法,以便新手可以理解这个表达式的结果。如何在这里告诉(&lt;$&gt;) = fmap?我不知道如何使用hoogle 和其他工具找到特定的实例定义(来源)。

编辑:更改了不正确的原始表达式以匹配以下缩减。

【问题讨论】:

    标签: haskell


    【解决方案1】:

    我发现最简单的方法是使用 GHCi 7.8 中提供的typed holes

    > (*10) <$> _a $ 1
    Found hole ‘_a’ with type: s0 -> b
    Where: ‘s0’ is an ambiguous type variable
           ‘b’ is a rigid type variable bound by
               the inferred type of it :: b at <interactive>:4:1
    Relevant bindings include it :: b (bound at <interactive>:4:1)
    In the second argument of ‘(<$>)’, namely ‘_a’
    In the expression: (* 10) <$> _a
    In the expression: (* 10) <$> _a $ 1
    

    所以这告诉我a :: s0 -&gt; b。接下来是弄清楚运算符的顺序:

    > :i (<$>)
    (<$>) :: Functor f => (a -> b) -> f a -> f b
    infixl 4 <$>
    > :i ($)
    ($) :: (a -> b) -> a -> b
    infixr 0 $
    

    所以这表示$ 是高度右关联的,并且鉴于它的类型,我们看到它的第一个参数必须是一个函数,所以a 必须是一个函数(双重确认)。这意味着(*10) &lt;$&gt; a $ 1((*10) &lt;$&gt; a) $ 1 相同,因此我们将首先关注(*10) &lt;$&gt; a

    > :t ((*10) <$>)
    ((*10) <$>) :: (Num a, Functor f) => f a -> f a
    > :t (<$> _a)
    Found hole ‘_a’ with type: f a
    Where: ‘a’ is a rigid type variable bound by
               the inferred type of it :: (a -> b) -> f b at Top level
           ‘f’ is a rigid type variable bound by
               the inferred type of it :: (a -> b) -> f b at Top level
    In the second argument of ‘(<$>)’, namely ‘_a’
    In the expression: (<$> _a)
    

    所以我们需要a 作为函子。有哪些可用实例?

    > :i Functor
    class Functor (f :: * -> *) where
      fmap :: (a -> b) -> f a -> f b
      (<$) :: a -> f b -> f a
            -- Defined in ‘GHC.Base’
    instance Functor Maybe -- Defined in ‘Data.Maybe’
    instance Functor (Either a) -- Defined in ‘Data.Either’
    instance Functor ZipList -- Defined in ‘Control.Applicative’
    instance Monad m => Functor (WrappedMonad m)
      -- Defined in ‘Control.Applicative’
    instance Control.Arrow.Arrow a => Functor (WrappedArrow a b)
      -- Defined in ‘Control.Applicative’
    instance Functor (Const m) -- Defined in ‘Control.Applicative’
    instance Functor [] -- Defined in ‘GHC.Base’
    instance Functor IO -- Defined in ‘GHC.Base’
    instance Functor ((->) r) -- Defined in ‘GHC.Base’
    instance Functor ((,) a) -- Defined in ‘GHC.Base’
    

    所以(-&gt;) r 恰好是一个,这太棒了,因为我们知道a 必须是一个函数。从Num 约束,我们可以确定r 必须与Num a =&gt; a 相同。这意味着(*10) &lt;$&gt; a :: Num a =&gt; a -&gt; a。然后我们将1 应用到它上面,我们会得到(*10) &lt;$&gt; a $ 1 :: Num a,其中a 是一些未知函数。

    所有这些都可以通过 GHCi 使用 :t:i 以及键入的孔来发现。当然,这涉及到相当多的步骤,但是当您尝试分解复杂的表达式时,它永远不会失败,只需查看不同子表达式的类型即可。

    【讨论】:

      【解决方案2】:

      GHCi 的建议很棒而且很正确,我也建议这样做。

      我也想建议Hoogle,因为启用即时搜索(在右侧顶部边栏有一个按钮),您可以快速搜索功能非常,它可以提供比 GHCi 更多的信息,更多,最好的部分是您不必提及在其中搜索的模块1。这与必须先导入的 GHCi 形成对比:

      ghci> :t pure
      <interactive>:1:1: Not in scope: ‘pure’
      ghci> :m +Control.Applicative
      ghci> :t pure
      pure :: Applicative f => a -> f a
      

      上面的 Hoogle 链接只是一个(来自 Haskell.org 网站)。 Hoogle 是一个程序,您也可以将其安装在您的机器上 (cabal install hoogle) 并从命令行 (hoogle your-query) 执行查询。
      旁注:您必须先运行hoogle data 才能收集信息。它需要wget/curl,所以如果你在Windows上,你可能需要首先在你的路径中获得this(当然,对于Windows来说是curl)。在 Linux 上它几乎总是内置的(如果你在 Linux 上没有它,只需 apt-get 它)。顺便说一句,我从不从命令行使用 Hoogle,它根本不那么易于访问,但它仍然非常有用,因为一些文本编辑器及其插件可以利用它。

      或者,您可以使用 FPComplete's Hoogle,这有时会更令人满意(因为根据我的经验,它已经知道更多的 3rd 方库。我只在那些“Hoogling 会话”中使用它)。

      顺便还有Hayoo!

      1 在 Hoogle 中,您可能有 >95% 的时间不必这样做,但 +Module 如果由于某种原因没有搜索到模块(就是这种情况),则可以导入模块有时用于第 3 方库)。
      您还可以通过 -Module 过滤掉模块。
      例如:destroyTheWorld +World.Destroyer -World.Destroyer.Mercy 找到 destroyTheWorld 并确保您没有寻找仁慈的方法(这对于不同版本的具有相同功能名称的模块非常方便,例如 Data.ByteString & Data.ByteString.LazyData.Vector & Data.Vector.Mutable 等)。

      Hoogle 还有一个很棒的优势,它不仅可以显示函数的签名,还可以带你到模块的 Haddock 页面,因此你还可以在这些页面中获得文档 +,如果可用,你可以点击每个函数右侧的“Source”以查看其实现方式以获得更多信息。

      这超出了问题的范围,但 Hoogle 也用于查询函数签名,这非常有用。如果我想要一个函数,它需要一个索引号和一个列表,并给我该索引中的元素,我想知道它是否已经内置,我可以在几秒钟内搜索它。
      我知道该函数接受一个数字和一个列表,并为我提供列表的一个元素,因此函数签名必须类似于以下内容:Int -&gt; [a] -&gt; a(或通常:Num a =&gt; a -&gt; [b] -&gt; b),并且两个实例都显示在那里确实是一个功能((!!)genericIndex)。

      GHCi 的优势在于您可以玩弄表达式、探索表达式等。很多时候处理抽象函数时意义重大。
      能够:l(oad) 也非常有帮助。

      如果您只是在寻找函数签名,则可以将 Hoogle 和 GHCi 结合使用。
      在 GHCi 中你可以输入:! cmd,GHCi 会在命令行中执行cmd,并打印结果。这意味着您也可以在 GHCi 中进行 Hoogle,例如:! hoogle void.

      【讨论】:

        【解决方案3】:

        启动ghci,:cd到你正在阅读的源码的基目录,:load你感兴趣的模块,然后使用:i命令获取信息:

        ghci> :i <$>
        (<$>) :: Functor f => (a -> b) -> f a -> f b
            -- Defined in `Data.Functor'
        infixl 4 <$>
        ghci> :i $
        ($) :: (a -> b) -> a -> b   -- Defined in `GHC.Base'
        infixr 0 $
        ghci> :i .
        (.) :: (b -> c) -> (a -> b) -> a -> c   -- Defined in `GHC.Base'
        infixr 9 .
        

        这会告诉您类型、定义的位置、关联性(infixlinfixr)和优先级(数字;越高越紧)。所以(*10) &lt;$&gt; a $ 1 读作((*10) &lt;$&gt; a) $ 1

        当您:load 一个模块时,该模块内范围内的所有名称都将在 ghci 内的范围内。这可能会令人讨厌的一个地方是,如果您的代码中有错误,那么您不能:i 里面的任何东西。在这些情况下,您可以注释掉行,使用undefined,并且可能还使用 behlkir 建议的类型孔(还没有玩太多)。

        当你使用它时,试试 ghci 中的:? 命令。

        【讨论】:

          猜你喜欢
          • 2010-09-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-08
          • 2023-04-09
          • 2011-01-07
          相关资源
          最近更新 更多