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

sev*_*evo 10 haskell

有没有办法从表达简化中解脱出来?

例如,给出这个表达式:

(+) <$> a <*> b $ 1
Run Code Online (Sandbox Code Playgroud)

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

fmap (+) a <*> b $ 1
Run Code Online (Sandbox Code Playgroud)

定义Data.Functor

(.) (+) a <*> b $ 1  
Run Code Online (Sandbox Code Playgroud)

FMAPControl.Monad.Instancesinstance Functor ((->) r)

等等.

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

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

bhe*_*ilr 6

我发现简单的方法是使用GHCi 7.8中提供的类型孔:

> (*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
Run Code Online (Sandbox Code Playgroud)

所以这告诉我a :: s0 -> b.接下来是弄清楚运营商的顺序:

> :i (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
infixl 4 <$>
> :i ($)
($) :: (a -> b) -> a -> b
infixr 0 $
Run Code Online (Sandbox Code Playgroud)

所以这说明$是高度右关联的,并且考虑到它的类型,我们看到它的第一个参数必须是一个函数,所以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)
Run Code Online (Sandbox Code Playgroud)

所以我们需要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’
Run Code Online (Sandbox Code Playgroud)

所以,(->) r正好是一个,这是真棒,因为我们知道,a必须是一个函数.从Num约束中,我们可以确定r必须与之相同Num a => a.这意味着(*10) <$> a :: Num a => a -> a.然后我们应用1它,我们得到(*10) <$> a $ 1 :: Num a,哪里a有一些未知的功能.

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


Mas*_*tic 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
Run Code Online (Sandbox Code Playgroud)

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

或者你可以使用FPComplete的Hoogle,这有时会更令人满意(因为根据我的经验,它已经知道更多第三方库.我只在那些"Hoogling会话"中使用它).

还有Hayoo!顺便说说.

1在Hoogle中,您可能> 95%的时间不必这样做,但是+Module如果出于某种原因导入模块,则不会进行搜索(有时候第三方库会出现这种情况).
您也可以过滤掉模块-Module.
例如:destroyTheWorld +World.Destroyer -World.Destroyer.Mercy找到destroyTheWorld并确保你没有看到这种仁慈的方式(这对于不同版本具有相同功能名称的模块非常方便,例如Data.ByteString&Data.ByteString.Lazy,Data.Vector&Data.Vector.Mutable等中的那些).

哦和Hoogle的另一个好处是,它不仅可以显示功能的签名,还可以带您进入模块的Haddock页面,因此您还可以在这些页面中获取文档+,如果可用,您可以单击"源"在每个功能的右侧,查看如何实现更多信息.

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

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

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


Lui*_*las 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 .
Run Code Online (Sandbox Code Playgroud)

它告诉您类型、定义位置、关联性(infixlinfixr)和优先级(数字;越高越紧)。所以(*10) <$> a $ 1读作((*10) <$> a) $ 1.

当您使用:load模块时,该模块内范围内的所有名称都将在 ghci 内范围内。这可能会让人烦恼的一个地方是,如果代码中有错误,那么您将无法:i在其中执行任何操作。在这些情况下,您可以注释掉行,使用undefined,也可能按照 behlkir 的建议使用类型化的洞(还没有使用过太多)。

当您执行此操作时,请尝试:?ghci 中的命令。