有没有办法从表达简化中消除痛苦?
例如,给出这个表达式:
(+) <$> a <*> b $ 1
我很想看到一个可以解释它意味着什么的工具。这对于初学者来说非常费力(在源中找到正确的实例函数定义,检查运算符优先级)以简化包含所有步骤的表达式:
fmap (+) a <*> b $ 1
请参阅Data.Functor
(.) (+) a <*> b $ 1
Control.Monad.Instances
instance Functor ((->) r)
中的fmap
等等。
编辑:为了澄清,我正在寻找一种使用实际函数定义重写表达式的方法,以便新手可以理解这个表达式的结果。如何在此处说明(<$>) = fmap
?我不知道如何使用hoogle
和其他工具找到特定的实例定义(源代码)。
编辑:更改了错误的原始表达式以匹配以下缩减。
答案 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.Lazy
,Data.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 .
它告诉您类型,定义的位置,关联性(infixl
或infixr
)和优先级(数字;更高更紧密)。因此(*10) <$> a $ 1
被视为((*10) <$> a) $ 1
。
当你:load
一个模块时,该模块中范围内的所有名称都将在ghci中的范围内。如果您在代码中出现错误,那么这可能会让您感到烦恼,那么您就无法:i
内部的任何内容。在这些情况下,你可以注释掉线路,使用undefined
,也可能像behlkir所建议的那样使用打字的洞(还没有玩那些太多的洞)。
在您了解情况时,请尝试使用ghci中的:?
命令。