Haskell编程语言的一些鲜为人知但有用的特性是什么.(我理解语言本身鲜为人知,但与我一起工作.即使是对Haskell中简单事物的解释,比如用一行代码定义Fibonacci序列,也会被我推翻.)
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)
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!
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的每个实例都应该满足两个定律:
但是,自由定理告诉我们,我们不需要费心去证明第一个,但鉴于第二个,它只是来自类型签名的"免费"!
fmap :: Functor f => (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)
你需要对懒惰有点小心,但这在原始论文中有所部分,并且在Janis Voigtlaender 最近关于自由定理的论文中有所体现seq.
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)
eph*_*ent 19
稳定的多行评论.
{- inside a comment,
{- inside another comment, -}
still commented! -}
Run Code Online (Sandbox Code Playgroud)
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)
多么酷啊!以节省您呼叫fromJust和head飘飞.
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)
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)
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)
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 (->),这对于那些不喜欢向后阅读函数应用程序的人来说非常好.
Mar*_*ijn 11
let 5 = 6 in ... 是有效的Haskell.
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对参数和列表的惰性评估.
灵活的模块导入和导出规范
导入和导出很好.
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)
如果您正在寻找列表或更高阶函数,它已经存在
标准库中有许多便利和高阶函数.
-- 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)
平等推理
Haskell,纯粹的功能允许您将等号读作真正的等号(在没有非重叠模式的情况下).
这允许您直接将定义替换为代码,并且在优化方面为编译器提供了关于何时发生的大量余地.
这种形式的推理的一个很好的例子可以在这里找到:
http://www.haskell.org/pipermail/haskell-cafe/2009-March/058603.html
这也很好地表现为法律或规则的形式,对于一个实例的有效成员,例如Monad法则:
通常可用于简化monadic代码.
增强的模式匹配
无可辩驳的模式
let ~(Just x) = someExpression
Run Code Online (Sandbox Code Playgroud)请参见模式匹配
并行列表理解
(特殊GHC功能)
fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]
Run Code Online (Sandbox Code Playgroud)
怠惰
无处不在的懒惰意味着你可以做像定义这样的事情
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
Run Code Online (Sandbox Code Playgroud)
但它在语法和推理方面也为我们提供了许多更微妙的好处.
例如,由于严格性ML必须处理值限制,并且非常小心地跟踪循环let绑定,但是在Haskell中,我们可以让每个让它们递归并且不需要区分val和fun.这从语言中删除了一个主要的句法疣.
这间接地产生了我们可爱的where条款,因为我们可以安全地移动可能会或可能不会在主要控制流程之外使用的计算,并让懒惰处理共享结果.
我们可以替换(几乎)所有需要take()并返回值的ML样式函数,只需对值进行惰性计算.有理由不时避免这样做,以避免CAF泄漏空间,但这种情况很少见.
最后,它允许不受限制的eta减少(\x -> f x可以用f代替).这使得像解析器组合器这样的组合定向编程比使用严格语言的类似结构更令人愉快.
这有助于您在以无点样式推理程序时,或者将它们重写为无点样式并减少参数噪音.
枚举
作为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)
| 归档时间: |
|
| 查看次数: |
18018 次 |
| 最近记录: |