Haskell的隐藏功能

Cla*_*diu 32 haskell

Haskell编程语言的一些鲜为人知但有用的特性是什么.(我理解语言本身鲜为人知,但与我一起工作.即使是对Haskell中简单事物的解释,比如用一行代码定义Fibonacci序列,也会被我推翻.)

  • 尝试限制Haskell核心的答案
  • 每个答案一个功能
  • 举一个示例和该功能的简短描述,而不仅仅是文档的链接
  • 使用粗体标题作为第一行标记要素

eph*_*ent 40

用户定义的控制结构

Haskell没有速记三元运算符.内置的if- then- else总是三元的,并且是一个表达式(命令式语言往往具有?:= expression,if= statement).但是,如果你想,

True ? x = const x
False ? _ = id
Run Code Online (Sandbox Code Playgroud)

将定义(?)为三元运算符:

(a ? b $ c)  ==  (if a then b else c)
Run Code Online (Sandbox Code Playgroud)

您必须使用大多数其他语言中的宏来定义自己的短路逻辑运算符,但Haskell是一种完全惰性的语言,所以它才有用.

-- prints "I'm alive! :)"
main = True ? putStrLn "I'm alive! :)" $ error "I'm dead :("
Run Code Online (Sandbox Code Playgroud)

  • 这太棒了 (2认同)
  • @ gorlum0:当然,只要给它一个比`$`更高的固定性,例如`infixr 1?`或类似的东西. (2认同)

yai*_*chu 28

Hoogle

Hoogle是你的朋友.我承认,它不是"核心"的一部分,所以cabal install hoogle

现在你知道"如果你正在寻找一个更高阶的函数,它已经存在了"(ephemient的评论).但是你怎么找到这个功能呢?随着hoogle!

$ hoogle "Num a => [a] -> a"
Prelude product :: Num a => [a] -> a
Prelude sum :: Num a => [a] -> a

$ hoogle "[Maybe a] -> [a]"
Data.Maybe catMaybes :: [Maybe a] -> [a]

$ hoogle "Monad m => [m a] -> m [a]"
Prelude sequence :: Monad m => [m a] -> m [a]

$ hoogle "[a] -> [b] -> (a -> b -> c) -> [c]"
Prelude zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
Run Code Online (Sandbox Code Playgroud)

hoogle-google程序员无法像在计算机的帮助下那样自己在纸上编写程序.但他和机器在一起是一个被迫不被忽视的人.

顺便说一句,如果你喜欢hoogle,请务必查看hlint!

  • Hoogle也可在http://haskell.org/hoogle/在线获取 (8认同)
  • 这是我遇到过的最好的API搜索工具.因为大多数时候你大致都知道你在寻找什么; 就像它应该做的那样.这使您可以搜索任何看起来像这样的东西. (3认同)

eph*_*ent 25

我的大脑爆炸了

如果您尝试编译此代码:

{-# LANGUAGE ExistentialQuantification #-}
data Foo = forall a. Foo a
ignorefoo f = 1 where Foo a = f
Run Code Online (Sandbox Code Playgroud)

您将收到此错误消息:

$ ghc Foo.hs

Foo.hs:3:22:
    My brain just exploded.
    I can't handle pattern bindings for existentially-quantified constructors.
    Instead, use a case-expression, or do-notation, to unpack the constructor.
    In the binding group for
        Foo a
    In a pattern binding: Foo a = f
    In the definition of `ignorefoo':
        ignorefoo f = 1
                    where
                        Foo a = f


Edw*_*ETT 22

自由定理

Phil Wadler向我们介绍了一个自由定理的概念,从那以后我们一直在Haskell中滥用它们.

Hindley-Milner风格类型系统的这些精彩文物通过使用参数化来告诉您函数不会做什么,从而帮助进行等式推理.

例如,Functor的每个实例都应该满足两个定律:

  1. forall f g.fmap f.fmap g = fmap(f.g)
  2. fmap id = id

但是,自由定理告诉我们,我们不需要费心去证明第一个,但鉴于第二个,它只是来自类型签名的"免费"!

fmap :: Functor f => (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)

你需要对懒惰有点小心,但这在原始论文中有所部分,并且在Janis Voigtlaender 最近关于自由定理的论文中有所体现seq.

  • +1我完全不知道这个,但它真的很酷; 为什么不是这个更高.如果您可以直接从类型签名派生结果,那么这将删除大量的证明工作.现在就读它. (5认同)

eph*_*ent 20

常用列表操作的简写

以下是等效的:

concat $ map f list
concatMap f list
list >>= f
Run Code Online (Sandbox Code Playgroud)

编辑

由于要求更多细节......

concat :: [[a]] -> [a]
Run Code Online (Sandbox Code Playgroud)

concat 获取列表列表并将它们连接成一个列表.

map :: (a -> b) -> [a] -> [b]
Run Code Online (Sandbox Code Playgroud)

map 在列表上映射函数.

concatMap :: (a -> [b]) -> [a] -> [b]
Run Code Online (Sandbox Code Playgroud)

concatMap相当于(.) concat . map:在列表上映射函数,并连接结果.

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b
    return :: a -> m a
Run Code Online (Sandbox Code Playgroud)

A Monad具有绑定操作,>>=在Haskell(或其含糖do等价物)中调用.列表,又名[],是Monad.如果我们替换[]m在上面:

instance Monad [] where
    (>>=) :: [a] -> (a -> [b]) -> [b]
    return :: a -> [a]
Run Code Online (Sandbox Code Playgroud)

Monad操作在列表中做什么是自然的事情?我们必须满足monad法则,

return a >>= f           ==  f a
ma >>= (\a -> return a)  ==  ma
(ma >>= f) >>= g         ==  ma >>= (\a -> f a >>= g)
Run Code Online (Sandbox Code Playgroud)

如果我们使用实施,您可以验证这些法律是否成立

instance Monad [] where
    (>>=) = concatMap
    return = (:[])

return a >>= f  ==  [a] >>= f  ==  concatMap f [a]  ==  f a
ma >>= (\a -> return a)  ==  concatMap (\a -> [a]) ma  ==  ma
(ma >>= f) >>= g  ==  concatMap g (concatMap f ma)  ==  concatMap (concatMap g . f) ma  ==  ma >>= (\a -> f a >>= g)
Run Code Online (Sandbox Code Playgroud)

事实上,这就是行为Monad [].作为示范,

double x = [x,x]
main = do
    print $ map double [1,2,3]
        -- [[1,1],[2,2],[3,3]]
    print . concat $ map double [1,2,3]
        -- [1,1,2,2,3,3]
    print $ concatMap double [1,2,3]
        -- [1,1,2,2,3,3]
    print $ [1,2,3] >>= double
        -- [1,1,2,2,3,3]
Run Code Online (Sandbox Code Playgroud)

  • 那应该是(>> =)=翻转concatMap. (3认同)

eph*_*ent 19

稳定的多行评论.

{- inside a comment,
     {- inside another comment, -}
still commented! -}
Run Code Online (Sandbox Code Playgroud)

  • 可嵌套的单行评论怎么样?;-) (8认同)

Nor*_*sey 19

广义代数数据类型.这是一个示例解释器,类型系统允许您覆盖所有情况:

{-# LANGUAGE GADTs #-}
module Exp
where

data Exp a where
  Num  :: (Num a) => a -> Exp a
  Bool :: Bool -> Exp Bool
  Plus :: (Num a) => Exp a -> Exp a -> Exp a
  If   :: Exp Bool -> Exp a -> Exp a -> Exp a 
  Lt   :: (Num a, Ord a) => Exp a -> Exp a -> Exp Bool
  Lam  :: (a -> Exp b) -> Exp (a -> b)   -- higher order abstract syntax
  App  :: Exp (a -> b) -> Exp a -> Exp b
 -- deriving (Show) -- failse

eval :: Exp a -> a
eval (Num n)      = n
eval (Bool b)     = b
eval (Plus e1 e2) = eval e1 + eval e2
eval (If p t f)   = eval $ if eval p then t else f
eval (Lt e1 e2)   = eval e1 < eval e2
eval (Lam body)   = \x -> eval $ body x
eval (App f a)    = eval f $ eval a

instance Eq a => Eq (Exp a) where
  e1 == e2 = eval e1 == eval e2

instance Show (Exp a) where
  show e = "<exp>" -- very weak show instance

instance (Num a) => Num (Exp a) where
  fromInteger = Num
  (+) = Plus
Run Code Online (Sandbox Code Playgroud)


Mar*_*ijn 18

顶级绑定中的模式

five :: Int
Just five = Just 5

a, b, c :: Char
[a,b,c] = "abc"
Run Code Online (Sandbox Code Playgroud)

多么酷啊!以节省您呼叫fromJusthead飘飞.


Jon*_*ran 17

可选布局

您可以使用显式大括号和分号代替空格(也称为布局)来分隔块.

let {
      x = 40;
      y = 2
     } in
 x + y
Run Code Online (Sandbox Code Playgroud)

......或者等价......

let { x = 40; y = 2 } in x + y
Run Code Online (Sandbox Code Playgroud)

... 代替 ...

let x = 40
    y = 2
 in x + y
Run Code Online (Sandbox Code Playgroud)

因为不需要布局,所以Haskell程序可以由其他程序直接生成.


San*_*ino 16

操作员固定

您可以使用infix,infixl或infixr关键字来定义运算符关联性和优先级.从参考文献中取得的例子:

main = print (1 +++ 2 *** 3)

infixr  6 +++
infixr  7 ***,///

(+++) :: Int -> Int -> Int
a +++ b = a + 2*b

(***) :: Int -> Int -> Int
a *** b = a - 4*b

(///) :: Int -> Int -> Int
a /// b = 2*a - 3*b
Output: -19
Run Code Online (Sandbox Code Playgroud)

中缀后的数字(0到9)允许您定义运算符的优先级,最强9.中缀表示没有关联性,而infixl表示左侧和不明信号的联系是正确的.

这允许您定义复杂的运算符以执行以简单表达式编写的高级操作.

请注意,如果将二进制函数放在反引号之间,也可以将它们用作运算符:

main = print (a `foo` b)

foo :: Int -> Int -> Int
foo a b = a + b
Run Code Online (Sandbox Code Playgroud)

因此,您还可以为它们定义优先级:

infixr 4 `foo`
Run Code Online (Sandbox Code Playgroud)

  • 是的,那些应该是反击.真的有人认为这是一个隐藏的功能吗? (3认同)

Jon*_*ran 16

seq并且($!) 只评估足够的东西以检查某些东西不是最低点.

以下程序只会打印"那里".

main = print "hi " `seq` print "there"
Run Code Online (Sandbox Code Playgroud)

对于那些不熟悉Haskell的人来说,Haskell一般来说是非严格的,这意味着只有在需要时才会评估函数的参数.

例如,以下打印"忽略"并成功终止.

main = foo (error "explode!")
  where foo _ = print "ignored"
Run Code Online (Sandbox Code Playgroud)

seq 众所周知,如果第一个参数是底部,则通过评估底部来改变该行为.

例如:

main = error "first" `seq` print "impossible to print"
Run Code Online (Sandbox Code Playgroud)

......或等效地,没有中缀......

main = seq (error "first") (print "impossible to print")
Run Code Online (Sandbox Code Playgroud)

......会因"第一次"错误而爆炸.它永远不会打印"不可能打印".

所以可能有点令人惊讶的是,即使seq是严格的,它也不会像渴望的语言评估那样评估某些东西.特别是,它不会尝试强制执行以下程序中的所有正整数.相反,它将检查[1..]不是底部(可以立即找到),打印"完成",然后退出.

main = [1..] `seq` print "done"
Run Code Online (Sandbox Code Playgroud)

  • [1 ..]简单地计算为1:[2 ..],即以1开头的列表,然后是另一个列表.如果你试图到达列表的末尾,它只会永远循环. (3认同)
  • 基本上,"bottom"是任何未定义的值,如调用错误的结果.永远循环的东西也被称为底部. (2认同)

eph*_*ent 15

避免括号

(.)($)功能Prelude都非常方便的固定性,让你避免许多地方括号.以下是等效的:

f (g (h x))
f $ g $ h x
f . g $ h x
f . g . h $ x
Run Code Online (Sandbox Code Playgroud)

flip 也有帮助,以下是等价的:

map (\a -> {- some long expression -}) list
flip map list $ \a ->
    {- some long expression -}
Run Code Online (Sandbox Code Playgroud)

  • 我更喜欢尽可能地删除括号,即使我在像C这样不太方便的语言中工作(假设每个人都在阅读我的代码*必须知道*所有运算符优先级).这是一个品味问题. (5认同)
  • 我更喜欢括号.它们更明确. (3认同)
  • 一旦你习惯了它,毫无意义的符号就是真实的表达.它只是一系列连续动词. (2认同)

eph*_*ent 15

漂亮的守卫

Prelude 定义 otherwise = True,使得完全保护条件非常自然地被读取.

fac n
  | n < 1     = 1
  | otherwise = n * fac (n-1)
Run Code Online (Sandbox Code Playgroud)


bee*_*boy 15

C风格枚举

结合顶级模式匹配和算术序列为我们提供了一种定义连续值的便捷方法:

foo : bar : baz : _ = [100 ..]    -- foo = 100, bar = 101, baz = 102
Run Code Online (Sandbox Code Playgroud)


eph*_*ent 13

可读的功能组成

Prelude定义(.)为数学函数组合; 也就是说,g . f首先应用f,然后应用于g结果.

如果您import Control.Arrow,以下是等效的:

g . f
f >>> g
Run Code Online (Sandbox Code Playgroud)

Control.Arrow提供了一个instance Arrow (->),这对于那些不喜欢向后阅读函数应用程序的人来说非常好.

  • 还要注意`(<<<)=(.)`:) (5认同)

Mar*_*ijn 11

let 5 = 6 in ... 是有效的Haskell.

  • 它的常量模式`5`与值'6`匹配,但由于let-bindings完全是懒惰的,所以永远不会尝试匹配. (5认同)
  • [爆炸] (3认同)

San*_*ino 10

无限的名单

既然你提到了斐波纳契,那么有一种非常优雅的方法可以从无限列表中生成斐波纳契数,如下所示:

fib@(1:tfib)    = 1 : 1 : [ a+b | (a,b) <- zip fib tfib ]
Run Code Online (Sandbox Code Playgroud)

@运算符允许您在1:tfib结构上使用模式匹配,同时仍将整个模式称为fib.

请注意,理解列表进入无限递归,生成无限列表.但是,您可以从中请求元素或操作它们,只要您请求有限数量:

take 10 fib
Run Code Online (Sandbox Code Playgroud)

您还可以在请求之前对所有元素应用操作:

take 10 (map (\x -> x+1) fib)
Run Code Online (Sandbox Code Playgroud)

这要归功于Haskell对参数和列表的惰性评估.

  • GHC中的语法扩展允许你现在写这个:fib = 1:1:[a + b | 一个< - fib | b < - tail fib]; 但我会写1:1:zipWith(+)fib(tail fib) (6认同)
  • 您的第一个示例实际上演示了Haskell的一个更隐藏的功能:您可以在定义值/函数时使用完整模式! (3认同)

eph*_*ent 9

灵活的模块导入和导出规范

导入和导出很好.

module Foo (module Bar, blah)  -- this is module Foo, export everything that Bar expored, plus blah

import qualified Some.Long.Name as Short
import Some.Long.Name (name)  -- can import multiple times, with different options

import Baz hiding (blah)  -- import everything from Baz, except something named 'blah'
Run Code Online (Sandbox Code Playgroud)


eph*_*ent 8

如果您正在寻找列表或更高阶函数,它已经存在

标准库中有许多便利和高阶函数.

-- factorial can be written, using the strict HOF foldl':
fac n = Data.List.foldl' (*) 1 [1..n]
-- there's a shortcut for that:
fac n = product [1..n]
-- and it can even be written pointfree:
fac = product . enumFromTo 1
Run Code Online (Sandbox Code Playgroud)


Edw*_*ETT 8

平等推理

Haskell,纯粹的功能允许您将等号读作真正的等号(在没有非重叠模式的情况下).

这允许您直接将定义替换为代码,并且在优化方面为编译器提供了关于何时发生的大量余地.

这种形式的推理的一个很好的例子可以在这里找到:

http://www.haskell.org/pipermail/haskell-cafe/2009-March/058603.html

这也很好地表现为法律或规则的形式,对于一个实例的有效成员,例如Monad法则:

  1. 返回a >> = f == fa
  2. m >> = return == m
  3. (m >> = f)>> = g == m >> =(\ x - > fx >> = g)

通常可用于简化monadic代码.


Dar*_*rio 6

增强的模式匹配

请参见模式匹配


Dar*_*rio 6

并行列表理解

(特殊GHC功能)

  fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]
Run Code Online (Sandbox Code Playgroud)


Edw*_*ETT 6

怠惰

无处不在的懒惰意味着你可以做像定义这样的事情

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
Run Code Online (Sandbox Code Playgroud)

但它在语法和推理方面也为我们提供了许多更微妙的好处.

例如,由于严格性ML必须处理值限制,并且非常小心地跟踪循环let绑定,但是在Haskell中,我们可以让每个让它们递归并且不需要区分valfun.这从语言中删除了一个主要的句法疣.

这间接地产生了我们可爱的where条款,因为我们可以安全地移动可能会或可能不会在主要控制流程之外使用的计算,并让懒惰处理共享结果.

我们可以替换(几乎)所有需要take()并返回值的ML样式函数,只需对值进行惰性计算.有理由不时避免这样做,以避免CAF泄漏空间,但这种情况很少见.

最后,它允许不受限制的eta减少(\x -> f x可以用f代替).这使得像解析器组合器这样的组合定向编程比使用严格语言的类似结构更令人愉快.

这有助于您在以无点样式推理程序时,或者将它们重写为无点样式并减少参数噪音.


bee*_*boy 5

枚举

作为Enum实例的任何类型都可以用在算术序列中,而不仅仅是数字:

alphabet :: String
alphabet = ['A' .. 'Z']
Run Code Online (Sandbox Code Playgroud)

包括您自己的数据类型,只需从Enum派生以获得默认实现:

data MyEnum = A | B | C deriving(Eq, Show, Enum)

main = do
    print $ [A ..]                 -- prints "[A,B,C]"
    print $ map fromEnum [A ..]    -- prints "[0,1,2]"
Run Code Online (Sandbox Code Playgroud)

  • 第一次看到它,令人惊讶的是`[A ..]`实际上是一个有限的枚举. (2认同)