Skip to content

Instantly share code, notes, and snippets.

@jackywyz
Created December 2, 2011 07:29
Show Gist options
  • Save jackywyz/1422195 to your computer and use it in GitHub Desktop.
Save jackywyz/1422195 to your computer and use it in GitHub Desktop.
Haskell总结

###haskell 基本类型,bool(True/False),list[],tuple(),int,Integer,Double,char'' ,string"",function(\x->x+1)

  1. wiki
  • packing
  • API链接
  • IDE:leksah
  • 测试工具QuickCheck,HUnit
  • 包管理工具cabal
  • 手动安装包: cd file_path
    runhaskell Setup configure
    runhaskell Setup build
    sudo runhaskell Setup install
  1. 编译运行
  • :m + Data.List Data.Map Data.Set追加模块到当前环境 ,还有:t,:k,:i, 且:h查看帮助
  • runhaskell hell.hs
  • ghc --make helloworld and then ./helloworld
  1. let/in 在函数的应用

    root s = 
      let p = 2
      in s*p + q
      where q = 10
  2. List类型

  • List是同质的,所有元素是相同类型,若是函数a->b,则各个元素in/out类型相同.
  • s = [1,2,3] ; s!!0 取值
  • 函数 null ,length, head,tail,last,init,sum,product,reverse,take,drop,maximum,elem,and(所有元素为True则True),zipWith
  • a = [1..2],coffescript 也有
  • 没有map,有Map.fromList [(5,'a'), (3,'b')] ! 5
  1. 匿名函数
  • (\a->a*2) 2
  • (*3),(+3)偏应用函数
  • (,) 相当 \x,y->(x,y)
  • id:: a->a , id 2 = 2
  1. 没有循环,用迭代代替

     [a | a <- [1,2,3]]
     if 2>3 then 2 else 3
    
  2. 门卫

     f a b 
      |a==b = b
      |a > b = a
      |otherwise = a+b  --  这里otherwise = True是关键字
  3. curry(多个参数,实际上是一个参数,返回一个函数作为结果) + 偏应用

  • f a b = a+b
  • g (a,b) = a+b
  • f = curry g
  • g = uncurry f
  • 若第二个参数给出的curry, 则使用中缀表示法,如s = (`elem` [1,2,3])为偏函数
  1. ".lhs" 文件以">"符号开始代码,其他的都是注释。

  2. 关键字

  • data
  • type
  • newtype 是临时的data定义
  • class
  • instance
  • qualified
  • 派生deriving [ Eq(相等), Ord(比较), Enum(枚举), Bounded(最大最小), Show and Read(串化和逆串化)]
  1. classes ,继承
class  (Eq a) => Ord a  where
   compare              :: a -> a -> Ordering
   (<), (<=), (>=), (>) :: a -> a -> Bool
   max, min             :: a -> a -> a

##Chapter 9

In / Out

  • unwords :: [String]->String
  • words :: String -> [String]
  • print,putStrLn等函数只能放在main中执行
  • if true then 1 else 2;
  • IO action 中 when 函数 : when True $ do $ something
  • 要认为putStrLn 输入一个串,输出一个IO action,当这个action执行的时候,输出结果到屏幕上。
  • do 语句不能嵌套其他的action。
  • 使用let时可不写in。 = 不相等

Files and streams

  • lines :: String->[String] , 对"\n"换行
  • unlines ::[String]->String,对"\n"换行
  • interact:: (String->String)->IO () ,通过喂给一个函数类型参数处理输入得到的串,输出期望的结构到屏幕

##Chapter 11 ###functors

  • 以Maybe,[] ,IO, r->a 类型举例。
  • (->)r类型 fmap = (.)函数组合。 =》 fmap (*3) (+100) 1 equals (*3) . (+100) $ 1 
  • fmap :: (a -> b) -> (f a -> f b), This is called lifting a function
  • functor laws
    1. fmap id = id
    2. fmap (f . g) = fmap f . fmap g

###Applicative 继承自Functor函子

  • 定义: Control.Applicative

     class (Functor f) => Applicative f where  
     pure :: a -> f a  
     (<*>) :: f (a -> b) -> f a -> f b  
    
     
     (<$>) :: (Functor f) => (a -> b) -> f a -> f b  
     f <$> x = fmap f x  

    --Applicative法则Law:

    • (pure f) <> x = A f <> x = fmap f x. 其中f是a->b函数,A是functor,x是A b
    • (pure id) <*> v = fmap id v = id v = v
    • pure (.) <> u <> v <> w = u <> (v <*> w)
    • pure f <*> pure x = pure (f x)
    • u <> pure y = pure ($ y) <> u
  • pure f <> x <> y <*>允许我们使用没有被functor的函数,这个函数带有我们期望的参数,可以来操作那些在functor上下文中的值。

  • ZipList: 列表的zip映射互相操作,有别与[]类型的互相遍历操作(双for循环)

  • () <$> [1,2] <> [2,4] 输出 [2,4,4,8]
  • getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100,100]
  • 【101,102,103】
  • liftA2 f a b = f <$> a <*> b :: (a->b->c)->f a->f b-> f c
  • sequenceA (x:xs) = liftA2 (:) x (sequenceA xs) :: [f a] -> f [a]

###newtype 关键字

  • 定义:
    1. 仅仅包装一个存在的类型到新的类型(仅仅名字不同),内部性质是一样的,只是叫不同名字。
    2. 相比data而言,没有装包和拆包的开销
    3. 只有单值单域构造器,data是多值多域的。
    4. 能像data一样使用派生关键字deriving
    5. 延迟评估,undefined 强行评估会抛异常。haskell 默认是Lazy的,如:head [1,2,undefined] is ok!
    6. newtype定义的类型是Lazy的: newtype CoolBool = CoolBool { getCoolBool :: Bool }
      helloMe (CoolBool _) = "hello"
      然后 helloMe undefined 输出 "hello",然而如果是使用data定义CoolBool则会进行评估而抛出异常.
  • 比较data,type,newtype:
    1. type 只是给已经存在的类型起一个别名而已,只是引用,本身没有构造器.
    2. newtype 包上一个存在类型A为另外一种类型B,性质没发生改变,用来区别A类型,通过record语法方便构造某些类型类的实例.只能有一个构造器和字 段. 是Lazy的。
    3. data 可以多构造器,多域,多可能性,而且会强制评估.

###Monoids

  • 定义,在包Data.Monoid

    1. 满足结合律的二分函数,函数的参数满足该函数的唯一性表示,即如 '',1n=n,则1即为唯一
    class Monoid m where  
    mempty :: m  
    mappend :: m -> m -> m  --(二分函数,不是通常意义上的append操作)
    mconcat :: [m] -> m  
    mconcat = foldr mappend mempty  --(默认实现,一般情况下使用默认)
    -- foldr 定义为 [a,b,c]:\z 同scala 的foldRight
    -- foldl 定义为 z/:[a,b,c] 同scala 的foldLeft
    1. Laws of the monoid:
    • mempty mappend x = x (唯一性)
    • x mappend mempty = x (唯一性)
    • (x mappend y) mappend z = x mappend (y mappend z) (结合律)
    1. 举例:
    • [a] 映射 mappend (++)
    • Num(Product/Sum) 映射 mappend (+)
    • Bool(Any/All) 映射 mappend (|| / &&)
    • Ordering 映射 mappend (EQ,LT,GT,compare)
    • Maybe 映射 mappend Maybe值
    instance Monoid a => Monoid (Maybe a) where --此处的=>为类型约束,当a是monoid实例时
                                                                  --,Maybe a是monoid实例
      mempty = Nothing  
      Nothing `mappend` m = m  
      m `mappend` Nothing = m  
      Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)  
  • Foldable数据结构(像手风琴样的折叠操作),例如list中的foldl,foldr

    1. Data.Foldable as F中,且已经默认预先导入.

    2. F.foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m 化简数据结构从多个monoid到最终一个monoid值, F.foldr和F.foldl 在foldMap定义后自由使用。

    3. 举例

     data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show, Read, Eq) 
     instance F.Foldable Tree where  
     foldMap f Empty = mempty  
     foldMap f (Node x l r) = F.foldMap f l `mappend`  
                            f x           `mappend`  
                            F.foldMap f r   

##Chapter 14 Zipper

  • 定义:(Tree a,[Direction])移动的软盘,能应用到任何数据结构上面,实现上是模式匹配大显神威,就像WIN7的文件目录导航操作一样。

    1. 复杂的数据结构的游标,比如Tree,可以随意“更新”(新生成一棵树)树操作,也就是对数据结构按照指定的动作(Breadcrumbs)遍历.
      data Direction = L | R deriving (Show) --遍历的动作方向 type Directions = [Direction]
      changeToP :: Directions-> Tree Char -> Tree Char
      changeToP (L:ds) (Node x l r) = Node x (changeToP ds l) r
    2. 在遍历的同时记住遍历Tree的顺序和节点,形成新的Tree和List(Breadcrumbs面包屑),能随意聚焦到任一子树和修改节点元素.
      goLeft :: (Tree a, Breadcrumbs) -> (Tree a, Breadcrumbs)
      goLeft (Node _ l _, bs) = (l, L:bs) (freeTree, []) -: goRight -: goLeft
    3. 可以回溯到子树的父节点.
      data Crumb a = LeftCrumb a (Tree a) | RightCrumb a (Tree a) deriving (Show)
      type Breadcrumbs a = [Crumb a]
      goLeft (Node x l r, bs) = (l, LeftCrumb x r:bs)
      goUp (t, RightCrumb x l:bs) = (Node x l t, bs)
    4. (Tree a, Breadcrumbs) 可up,可down,就想拉链一样,也可以理解为“Focus聚焦”,只关心数据结构的部分上下文.
      type Zipper a = (Tree a, Breadcrumbs a)
  • 使用Zipper游标操作Tree

    1. 修改节点元素: modify f (Empty, bs) = (Empty, bs) 。 其中f::a->a
    2. 添加节点 :
      attach :: Tree a -> Zipper a -> Zipper a
      attach t (_, bs) = (t, bs)
    3. 回到根节点:
      topMost :: Zipper a -> Zipper a
      topMost (t,[]) = (t,[])
      topMost z = topMost (goUp z)
  • 应用到Lists上

####模块的导出

module ModName(模块名) where
data  T1 .....
data  T2 .....
func1 ....
func2 ....`

模块名要与包含模块的文件名(出去扩展名.hs)相同。 可以控制模块被导入后,别人能够使用的内容。格式如下 如果你不想fuc2被使用,则可以这样写:

module ModName (
  T1,
  T2,
  func1)
 where
data Card = Card Suit Face
data Suit = Hearts
 | Spades
 | Diamonds
 | Clubs
data Face = Jack
 | Queen
 | King
 | Ace
 | Number Int

如果在你的模块中有如上的自定义数据类型。如果你不希望别人使用其中的构造器(constructors),你可以这样写:

module ModName ( Card(),
   Suit(),
   Face())
 where

如果你希望别人使用其中的构造器(constructors),可以这样写:

module ModName ( Card(..),
   Suit(..),
   Face(..))
 where

".."代表了所有的构造器(constructors)。

####模块的导入 使用方式: import ModName 当导入模块后,你就可以直接使用模块里面的函数与数据类型了。比如在上一个Mod里面定义的func1,你可以直接用func1 .... 或者 ModName.func1 ....。使用后面的方式可以避免一些不必要的混淆,比如你在现在的模块里又定义了一个func1。 import qualified ModName 强制使用ModName.func1 ....,否则报错。 调用模块中特定的函数或类型 import ModName (func1) 那么在目前的文件中你只能使用ModName的func1,其他的你不能使用。

不调用模块中特定的函数或类型 import ModName hiding (func1) 那么在目前的文件中你不能使用ModName的func1,其他的你都可以使用。 重命名模块: 如果模块的名字太长了,这样做很有用。 import (qualified) ModName as AnotherName 以上介绍的导入方式可以混合使用。

####Hierarchical Imports 分级导入 并非haskell标准规定的。它的意义是一个在haskell调用目录下可以搜索模块的功能。 eg: E:\Haskell是haskell的编译路径。你的模块Mos1,在E:\Haskell\Mos下。那么你可这样导入模块Mos1,而不用担心编译报告找不到模块。 import Mos.Mos1 第一个Mos是文件夹的名字。

{-
Monad是一种规范,它作用于任何类型,只为在类型
的上面添加一个“构造器”用于标示某些状态。这些状态是不可以被“洗掉”的,这样就
很明显地指示出了程序的状态特征,就像是“指示剂”一样地明确,另外,它是一个编译
时的东西,对运行时完全没有效率影响!
-}
newtype Reader e a = Reader { runReader :: (e -> a) }
instance Monad (Reader e) where -- 这里的(Reader e是类型构造器)
return a = Reader $ \e -> a
(Reader r) >>= f = Reader $ \e -> runReader (f (r e)) e
class MonadReader m e | e -> m where
ask :: m e
local :: (e -> e) -> m a -> m a
{-
ghci -XMultiParamTypeClasses -XFunctionalDependencies -X
FlexibleInstances
-}
instance MonadReader (Reader e) e where
ask = Reader id
local f c = Reader $ \e -> runReader c (f e)
reader :: Reader String Int
reader = do
x <- Reader $ \e -> length(e)
return x

###下划线 _ 占位符,表示可以是任意值。用在模式匹配case of ,还有方法和类型定义是替代任何参数,以及函子和单子的定义 ###两个冒号 :: 符号 :: 读作 ” 具有类型”, 比如 x :: y 形式的内容 就应该理解成: 表达式x具有类型y 有几种情况, 一种是 函数定义,冒号前是 函数名称, 冒号后 是函数的输入输出的类型定义 另外就是 列表的类型定义, 如 [1,2,3,4,5] :: [Int] 表示 [1,2,3,4,5] 是一个Int类型的列表 ['a', 'a', 'b'] :: String 表示 一个字符列表, 也就是字符串

很多时候都不需要进行类型声明。那是因为 Haskell 可以暗中推断,不必声明之。也就是说,如果省略 :: 和后面的类型,Haskell编译器会推断这个表达式的类型。

###两横 (两个减号) – 单行注释

###'{}' 和分号 ';' 来构造程序块

###大括号和减号 {- -} 括起来的部分都是注释, 前括号 和 后括号 可以放在不同的行

###右箭头 -> 定义函数的输入输出的类型, 也就是定义函数的类型 1)如果只有输入, 没有输出, 就没有箭头 2) 有多个输入参数, 依次用 -> 相连, 最后一个才是 输出

另外,在case表达式中, 连接模式和相应的结果, 如

firstDigit :: String -> Char
firstDigit st
      = case (digits st) of
               []        -> '\0'
               (x:_)   ->  x

一个case 表达式可用于区分不同的分支选择, 在上面这个例子中,是空列表和非空列表

###左箭头 <- 属于, 在list comprehension中, 表示一个元素 属于 哪个列表 (集合) 这个跟数学符号 表示 元素属于哪个集合,是一样的意思

另外一种用法 类似于 赋值 do { n <- readLn ; print (n^2) } 这段代码,就是将从标准输入读取到的数字 赋值给 n, 然后打印出n的平方 但是只限于 IO,Maybe,[] action, 对于普通的函数, 必须用 let = 来赋值

###vertical bar符号 | 竖线符号, 这个符号,在C语言中表示按位与 在Haskell中表示 guide 用来在函数中表示不同的情况, 有点类似与C语言的switch case的意思 比如,求两整数之间的最大值

max :: Int -> Int -> Int max x y --max是函数名, x y是两个形式参数 | x >=y = x --表示如果x>=y 这个条件成立,最大值就等于x | otherwise = y guide是模式匹配的扩展, 可以将竖线读作 ” 当…时”

另外,在list comprehension中, 竖线 分割列表的定义和描述 设列表ex为[2, 4, 7] 则列表概括 [2*n | n <- ex] 表示列表 [4, 8, 14]

第3种情况,就是用data定义数据时用来枚举不同的情况

data Maybe a = Nothing | Just a data Either a b = Left a | Right b

data Color = Red | Orange | Yellow | Green | Blue | Purple | White | Black 它与guide的区别是,第1种情况的前面,不需要 | 符号, 而guide的每种情况都需要

###元组 (,,) 括号里有若干个用逗号分割开的元素 这个概念跟python中的tuple是差不多的

###列表 [,,] 方括号中包含 若干个逗号分割开的元素 跟python中list也是差不多的

++ 列表的连接, 比如 “Hello” ++ “World” 的结果是 “HelloWorld”

=> (Eq a) => a -> a -> Bool 它左边的部分叫做类型约束(type constraint), 有人把 类型约束叫做 context

下划线 _ 通配符, 在模式匹配中, 可以与任何参数都匹配 比如 x:_ 匹配非空列表

###单冒号 : 在列表中,表示连接, 如 3:[] = [3] 以及 2:[3] = [2,3] 冒号称之为cons运算

###反引号 英文中叫做gave accent, 或者backquote , backtick. **函数的中缀表示法**。 键盘中中, 它在 ~ 符号的下面, 也就是按住shift会输入~, 不按shift就会输入

###两个感叹号 !! 取列表的下标元素 如 let x = [abcdefg] x!!6 就为’g’

###关键字 case class data default deriving 这个不常见 do else if import in infix 这3个也不常见 infixl infixr instance let module newtype of then type where

这样说来,关键字是没有多少的

###单引号’

  1. 用单引号将单个字符括起来, 这与C语言中的语法是一致的

  2. 单个单引号是函数名的一部分, 比如 foldl’

作为高阶函数参数的函数通常命名冠以 f、g 等,但有时也像类型变量后门带一些数字进行修饰那样, 函数名后面会带单引号 ‘ 进行修饰 ,例如像 g’ 在以后的例子中,你可以把它读作 “Jee-prime”,并且它被认为是一个与 g 函数有特定关系(a helper or the like)的函数。

比如

foldl — 从左到右遍历它的结构 foldl’ — a fold that is strict in its accumulator, “‘” is used to indicate a strict variant of a function

###点号.

函数复合

(f.g) 3

###美元符号 $

函数应用 function application

在标准库Prelude中, ($)的定义为

f $ x = f x

这个函数的优先级非常低, 这意味着它可以用来代替括号

foo x y = bar y (baz (fluff (ork x)))

可以改写成

foo x y = bar y $ baz $ fluff $ ork x

这基本上类似于 函数复合 点号的语法 ###反斜线
匿名函数,lamda函数 例如 \xs -> length xs > 15 就定义了一个lamda函数,它的参数是xs, 左右就是判断 xs的长度是否大于 15

@davidxifeng
Copy link

(*3),(+3)偏应用函数

我觉得翻译成 部分应用函数 比较好。好像这个用的比较多。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment