学习Haskell - 如何简化表达式?

时间:2014-08-28 22:40:52

标签: haskell

有没有办法从表达简化中消除痛苦?

例如,给出这个表达式:

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

我很想看到一个可以解释它意味着什么的工具。这对于初学者来说非常费力(在源中找到正确的实例函数定义,检查运算符优先级)以简化包含所有步骤的表达式:

fmap (+) a <*> b $ 1

请参阅Data.Functor

中的definition
(.) (+) a <*> b $ 1  

Control.Monad.Instances

中的instance Functor ((->) r)中的fmap

等等。

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

编辑:更改了错误的原始表达式以匹配以下缩减。

3 个答案:

答案 0 :(得分:6)

我发现简单的方法是使用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 -> b。接下来是弄清楚运营商的顺序:

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

所以这说明$是高度正确联想的,并且根据它的类型我们看到它的第一个参数必须是一个函数,所以a必须是功能(双重确认)。这意味着(*10) <$> a $ 1((*10) <$> a) $ 1相同,因此我们首先关注(*10) <$> 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’

所以(->) r碰巧是一个,这很棒,因为我们知道a必须是一个函数。从Num约束,我们可以确定r必须与Num a => a相同。这意味着(*10) <$> a :: Num a => a -> a。然后我们将1应用于它,我们得到(*10) <$> a $ 1 :: Num a,其中a是一些未知函数。

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

答案 1 :(得分:5)

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的卷曲,当然)。在Linux上,它几乎总是内置的(如果你不在Linux上,只有apt-get它)。顺便说一句,我从不使用命令行中的Hoogle,它根本不是可访问的,但它仍然非常有用,因为一些文本编辑器及其插件可以利用它。

或者你可以使用有时更令人满意的FPComplete's Hoogle(因为根据我的经验,它已经知道更多的第三方库。我只在那些&#34; Hoogling会话&#34;)中使用它。 / p>

顺便说一下,还有Hayoo!

1 在Hoogle中,您可能> 95%的时间不得不这样做但+Module导入模块如果由于某种原因它没有被搜索(有时第三方图书馆就是这种情况) 您还可以按-Module过滤掉模块 例如:destroyTheWorld +World.Destroyer -World.Destroyer.Mercy找到destroyTheWorld并确保您不会以仁慈的方式去做(这对于不同版本具有相同功能名称的模块非常方便,例如Data.ByteString&amp; Data.ByteString.LazyData.Vector&amp; Data.Vector.Mutable等中的内容。

哦和Hoogle的另一个好处是,它不仅会向您显示功能的签名,还可以将您带到模块的Haddock页面,因此您还可以在这些页面中获得文档+ ,如果可用,您可以点击&#34;来源&#34;在每个功能的右侧,查看如何实现更多信息。

这超出了问题的范围,但是Hoogle也用于查询功能签名,这只是...非常有帮助。如果我想要一个带索引号和列表的函数并给我该索引中的元素,我想知道它是否已内置,我可以在几秒钟内搜索它。
我知道该函数需要一个数字和一个列表,并给我一个列表元素,所以函数签名必须看起来像这些行:Int -> [a] -> a(或一般来说:Num a => a -> [b] -> b),以及两个实例表明确实有一个功能((!!)genericIndex)。

GHCi的优势在于你可以玩表达,探索它们等等。很多时候处理抽象函数意味着很多。
能够:l(oad)非常有帮助。

如果您只是寻找功能签名,则可以将Hoogle和GHCi结合使用 在GHCi中,您可以键入:! cmd,GHCi将在命令行中执行cmd,并打印结果。这意味着您也可以在GHCi内部使用Hoogle,例如:! hoogle void

答案 2 :(得分: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) <$> a $ 1被视为((*10) <$> a) $ 1

当你:load一个模块时,该模块中范围内的所有名称都将在ghci中的范围内。如果您在代码中出现错误,那么这可能会让您感到烦恼,那么您就无法:i内部的任何内容。在这些情况下,你可以注释掉线路,使用undefined,也可能像behlkir所建议的那样使用打字的洞(还没有玩那些太多的洞)。

在您了解情况时,请尝试使用ghci中的:?命令。