Haskell lambda 与 $ 与 函数组合

发布于
分类 Haskell
标签 Haskell

lambda

lambda就是匿名函数,有些时候我们会需要一个函数而这个函数可能只用到一次,并没有重用的场景,我们就可以搞一个 临时 的匿名函数来满足我们的计算。

(\xs -> length xs > 10)

lambda首先是一个\,后面是用空格分隔的参数,->后边就是函数体。通常会用括号括起来。

$

$函数,也叫作函数调用符,它的定义如下

($) :: (a -> b) -> a -> b  
f $ x = f x  

普通的函数调用符有最高的优先级,而 $ 的优先级则最低。用空格的函数调用符是左结合的,如 f a b c 与 ((f a) b) c 等价,而 $ 则是右结合的

$是优先级最低的中缀右结合函数,从签名来看,只是个函数调用符,相当于在右边加括号

tip: $是个中缀函数,要求左边是函数,右边是其参数

> max 5 3 * 2 + 1
11
> max 5 $ 3 * 2 + 1
7

f (g (z x))f $ g $ z x 等价

函数组合

函数组合用.函数来实现,.函数的定义为:

(.) :: (b -> c) -> (a -> b) -> a -> c  
f . g = \x -> f (g x)  

函数组合的用处之一就是生成新函数,并传递给其他函数。
假设我们有一个数字组成的list,我们要把它其中每个元素转成负数,在使用函数组合之前我们可能会这样实现:

Prelude> map (\x -> negate (abs x)) [1,2,-3,4,5,-6]
[-1,-2,-3,-4,-5,-6]

tip: 先用abs函数取绝对值,再用negate函数取反

用函数组合的话就可以这样实现:

Prelude> map (negate . abs) [1,2,-3,4,5,-6]
[-1,-2,-3,-4,-5,-6]

函数组合的另一用途就是定义 point free style (也称作 pointless style) 的函数。以下面的函数为例:

sum' :: (Num a) => [a] -> a     
sum' xs = foldl (+) 0 xs

等号的两端都有个 xs。由于有柯里化 (Currying),我们可以省掉两端的 xs。foldl (+) 0 回传的就是一个取一 List 作参数的函数,我们把它修改为 sum' = foldl (+) 0,这就是 point free style。下面这个函数改成point free style就是:

fn x = ceiling (negate (tan (cos (max 50 x)))) 
fn = ceiling . negate . tan . cos . max 50

...

阅读全文 »

Haskell 函数语法

发布于
分类 Haskell
标签 Haskell

模式匹配

Prelude> let { lucky' :: Integral a => a -> String; lucky' 7 = "seven"; lucky' x = "other" }
Prelude> lucky' 7
"seven"
Prelude> lucky' 10
"other"
Prelude> lucky' "a"

<interactive>:39:1: error:
    • No instance for (Integral [Char]) arising from a use of ‘lucky'’
    • In the expression: lucky' "a"
      In an equation for ‘it’: it = lucky' "a"

tip: 在8.8.1版本ghci中如果按照书中的写法是会报没有匹配项的错误的,按照let { ... } 写法则没有问题

在调用lucky时,模式会从上到下进行检查,一旦有匹配则对应的函数体便被应用。

如果我们制定的匹配模式不全时,传入一个没有被任何模式匹配到的参数时就会报错。

对Tuple同样可以使用模式匹配:

addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)  
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)  

first :: (a, b, c) -> a  
first (x, _, _) = x 

List Comprehension 也可以用模式匹配:

Prelude> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]  
Prelude> [a+b | (a,b) <- xs]
[4,7,6,8,11,4]

对List也可以用模式匹配:

Prelude> let {sumList :: Num a => [a] -> a; sumList [] = 0; sumList (x:xs) = x + sumList xs }
Prelude> sumList [1,2,3]
6

Guards

bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"  
    | weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise                 = "You're a whale, congratulations!"    

guard由跟在函数名及参数后边的竖线标志,通常竖线都是靠右一个缩进排成一列。一个guard就是一个布尔表达式,如果是True,就使用对应的函数体。最后的一个guard往往是otherwise,它的定义就是简单一个otherwise = True。

通过guard实现自己的compare函数

-- myCompare.hs
myCompare :: Ord a => a -> a -> Ordering
a `myCompare` b 
    | a > b = GT
    | a < b = LT
    | otherwise = EQ
Prelude> :l myCompare.hs
[1 of 1] Compiling Main             ( myCompare.hs, interpreted )
Ok, one module loaded.
*Main> 1 myCompare 2

<interactive>:2:1: error:
    • Non type-variable argument
        in the constraint: Num ((a -> a -> Ordering) -> t1 -> t2)
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        it :: forall a t1 t2.
              (Ord a, Num t1, Num ((a -> a -> Ordering) -> t1 -> t2)) =>
              t2
*Main> 1 `myCompare` 2
LT
*Main> 2 `myCompare` 1
GT
*Main> 1 `myCompare` 1
EQ

where 关键字

上面例子中的bmiTell函数weight / height ^ 2重复计算了3次,可以利用where修改:

-- bmiTell.hs

bmiTell :: RealFloat a => a -> a -> String
bmiTell weight height 
    | bmi <= 18.5 = "case 1"
    | bmi <= 25.0 = "case 2"
    | bmi <= 30.0 = "case 3"
    | otherwise = "otherwise"
    where bmi = weight / height ^ 2
*Main> :l bmiTell.hs
[1 of 1] Compiling Main             ( bmiTell.hs, interpreted )
Ok, one module loaded.
*Main> bmiTell 130 180
"case 1"

还可以继续修改:

bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | bmi <= skinny = "You're underweight, you emo, you!"  
    | bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | bmi <= fat    = "You're fat! Lose some weight, fatty!"  
    | otherwise     = "You're a whale, congratulations!"  
    where bmi = weight / height ^ 2  
          skinny = 18.5  
          normal = 25.0  
          fat = 30.0 

函数在where绑定中定义的名字只对当前函数可见。 where绑定也可以使用模式匹配

...
where bmi = weihgt / height ^ 2
    (skinny, normal, fat) = (18.5, 25.0, 30.0) 

where绑定可以定义名字也可以定义函数:

calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
calcBmis xs = [bmi w h | (w, h) <- xs] 
    where bmi weight height = weight / height ^ 2  

let关键字

let绑定是个表达式,允许在任何地方定义局部变量,而对不同的guard不可见。let也可以使用模式匹配

cylinder :: (RealFloat a) => a -> a -> a  
cylinder r h = 
    let sideArea = 2 * pi * r * h  
        topArea = pi * r ^2  
    in  sideArea + 2 * topArea  

let的格式为let [binging] in [expression]。let中绑定的名字仅在in中可见,let中的名字必须对齐在一列。

let是个表达式,而where是个语法结构。因为let是个表达式,所以let可以随处安放:

Prelude> [let square x = x * x in (square 5, square 3, square 2)]
[(25,9,4)]

上面的例子中let定义了一个函数。

若要在一行中绑定多个名字,可以用分号将他们分开:

Prelude> (let a = 100; b = 200; c = 300 in a*b*c, let foo = "Hey"; bar = "there" in foo ++ bar)
(6000000,"Heythere")

tip: 最后那个绑定后面的分号不是必须的,可以加上可以去掉

可以用let改写上面的calcBmis函数:

calcBmis xs = [bmi w h | (w, h) <- xs, let bmi = w / h ^ 2]

List Comprehension 中 let 绑定的样子和限制条件差不多,只不过它做的不是过滤,而是绑定名字。let 中绑定的名字在输出函数及限制条件中都可见。这一来我们就可以让我们的函数只返回胖子的 bmi 值:

calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0] 

在 (w, h) <- xs 这里无法使用 bmi 这名字,因为它在 let 绑定的前面。

在 List Comprehension 中我们忽略了 let 绑定的 in 部分,因为名字的可见性已经预先定义好了。不过,把一个 let...in 放到限制条件中也是可以的,这样名字只对这个限制条件可见。在 ghci 中 in 部分也可以省略,名字的定义就在整个交互中可见。

Prelude> let a = 1
Prelude> a
1

Case 表达式

head' :: [a] -> a  
head' xs = case xs of [] -> error "No head for empty lists!"  
                      (x:_) -> x  

case的语法:

case expression of pattern -> result  
                   pattern -> result  
                   pattern -> result  
                   ...  

expression匹配符合的模式,如果符合则执行。实际上上面的模式匹配是case的语法糖而已。

函数参数的模式匹配只能用在定义函数时使用,而case可以用在任何地方:

describeList :: [a] -> String  
describeList xs = "The list is " ++ case xs of [] -> "empty."  
                                               [x] -> "a singleton list."   
                                               xs -> "a longer list." 

...

阅读全文 »

Haskell Type与Typeclass

发布于
分类 Haskell
标签 Haskell

Type

ghci中可以用:t检测表达式的类型

Prelude> :t "a"
"a" :: [Char]

函数也有类型,编写函数时给一个明确的类型声明是一个好习惯

removeNonUppercase :: [Char] -> [Char]
removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]

我们可以这样解读这个函数的类型:removeNonUppercase这个函数接收一个Char List类型的参数返回一个Char List类型的返回值

addThree :: Int -> Int -> Int -> Int
addThree x y z = x + y + z

参数之间由->分隔,这样解读这个函数的类型:addThree这个函数接收3个Int类型的参数返回一个Int类型的返回值。

tip: 按照其他语言中的习惯,Int,Int,Int -> Int好像看起来更为恰当一些,但实际haskell中->只有一个作用:它标识一个函数接收一个参数并返回一个值,其中->符号左边是参数的类型,右边是返回值的类型。haskell中所有函数都是只接收一个参数的,所有函数都是currying的。

常见类型

  • Int 整数,与平台位数相关
  • Integer 无限大整数
  • Float 单精度浮点数
  • Double 双精度浮点数
  • Bool
  • Char

Tuple的类型取决于它的长度与其中项的类型,空Tuple也是一个类型,它只有一个值()

Type variables

以head函数为例

Prelude> :t head
head :: [a] -> a

可以看到这里有个a,而a明显不是一个具体的类型,类型首字母必须是大写的,那它是什么呢,它实际上是一个类型变量,a可以是任意类型。

tip: 与其他语言中的泛型generic很像

使用到类型变量的函数被称为“多态函数”。可以这样解读head函数的类型:head函数接收一个a类型的List参数(即任意类型的参数)返回一个a类型的返回值(参数与返回值的类型必须是一样的,都是a类型)
fst函数的类型:

Prelude> :t fst
fst :: (a, b) -> a

可以看到fst取一个包含两个型别的 Tuple 作参数,并以第一个项的型别作为回传值。这便是 fst 可以处理一个含有两种型别项的 pair 的原因。注意,a 和 b 是不同的型别变量,但它们不一定非得是不同的型别,它只是标明了首项的型别与回传值的型别相同。

Typeclass

如果一个类型属于某个typeclass,那它必定实现了Typeclass所描述的行为。

tip: 跟OOP中的接口很像

==函数的类型声明为例:

Prelude> :t (==)
(==) :: Eq a => a -> a -> Bool

这里的Eq就是typeclass, 这里意思是说a这个type必须是Eq的一个实现(相当于OOP中的a implement Eq)
=>符号左边的部分叫做类型约束

Eq这个Typeclass提供了判断相等性的接口,凡是可比较相等性的类型必定属于Eq class

elem函数的类型为:(Eq a)=>a->[a]->Bool这是因为elem函数在判断元素是否存在于list中时使用到了==的原因。

Show的成员为可用字符串表示的类型,操作Show Typeclass最常用的函数表示show。它可以取任一Show的成员类型并将其转为字符串

Prelude> show [1,2,3]
"[1,2,3]"
Prelude> show True
"True"

ReadShow相反,read函数可以将字符串转为Read的某成员类型

Prelude> read "5" - 2 
3
Prelude> read "True" || False
True

但是执行下面的代码,就会提示错误:

Prelude> read "5"
*** Exception: Prelude.read: no parse

这是因为haskell无法推导出我们想要的是一个什么类型的值,read函数的类型声明:

Prelude> :t read
read :: Read a => String -> a

它的回传值属于Read Typeclass,但是如果我们用不到这个值,它就无法推导出这个表达式的类型。所以我们需要在表达式后跟::的类型注释,以明确其类型:

Prelude> read "5" :: Int
5

...

阅读全文 »

Haskell 基础

发布于
分类 Haskell
标签 Haskell

第一个函数

创建doubleMe.hs文件,编写如下代码:

doubleMe x = x + x

保存,打开ghci,输入

Prelude> :l doubleMe.hs

这样我们就加载了我们的doubleMe函数,然后就可以调用这个函数:

Prelude> doubleMe 10
20

tip: 如果修改doubleMe.hs文件需要重新导入的话可以执行:reload doubleMe.hs或者:r doubleMe.hs重新导入

if语句

Haskell中的if语句与其他语言不同,else是不可以省略的

doubleSmallNum x = if x > 10 then x else x * 2

Haskell 中的 if 语句的另一个特点就是它其实是个表达式,表达式就是返回一个值的一段代码:5 是个表达式,它返回 5;4+8 是个表达式;x+y 也是个表达式,它返回 x+y 的结果。正由于 else 是强制的,if 语句一定会返回某个值,所以说 if 语句也是个表达式。

List

列表由方括号以及被逗号间隔的元素组成:

Prelude> [1,2,3]
[1,2,3]

空列表:[],列表中所有元素必须是同一类型。

列表操作符

用 ++ 操作符连接两个list

Prelude> [1,2,3] ++ [4,5,6]
[1,2,3,4,5,6]

用 : 连接一个元素到list头,它读作“cons”即construct简称

Prelude> 1:[2,3]
[1,2,3]

但是[2,3]:1是不被允许的,因为:的第一个参数必须是单个元素,第二个参数必须是list

字符与字符串

Prelude> "this is string"
this is string

双引号表示字符串。单个字符用''表示

Prelude> 't'
t

字符串实际是字符列表,

Prelude> 't' : "his is string"
this is string
Prelude> "this is" ++ " string"
this is string

操作

从list中取值使用!!(相当于其他语言中的arr[index])

Prelude> let l = [1,2,3]
Prelude> l!!1
2

上面的例子就是从列表l中取下标为1的元素
list可以用来装list:

Prelude> let l = [[1,2,3], [1,2,3,4], [1,2,3,4,5]]

haskell不要求每个元素的长度一致,但要求类型必须一致

  • head函数取list第一个元素
  • tail函数取list除第一个元素之后的全部
  • last返回list最后一个元素
  • init返回一个除去list最后一个元素的全部
  • length返回list长度
  • null判断list是否为空,如果是空返回True,否则False
  • reverse 反转list
  • take 返回前几个元素
  • maximum 返回最大元素
  • minimun 返回最小元素
  • sum 返回所有元素之和,product返回积
  • elem 判断一个元素是否存在于list中,通常中缀调用
Prelude> tail [[1,2,3], [1,2,3,4], [1,2,3,4,5]]
[[1,2,3,4], [1,2,3,4,5]]
Prelude> init [[1,2,3], [1,2,3,4], [1,2,3,4,5]]
[[1,2,3], [1,2,3,4]]
Prelude> reverse [1,2,3]
[3,2,1]
Prelude> take 2 [1,2,3]
[1,2]
Prelude> 1 `elem` [1,2,3]
True

Range

可以用列表符号来表示一系列元素,haskell会自动推导:

Prelude> [1..10]
[1,2,3,4,5,6,7,8,9,10]

Prelude> [1.0, 1.25, ..2.0]
[1.0,1.25,1.5,1.75,2.0]

Prelude> [1, 4, 15]
[1, 4, 7, 10, 13]

之所以没有输出15是因为15不属于我们定义的系列元素

List Comprehension

Prelude> [x*2 | x <- [1...10]]
[2,4,6,8,10,12,14,16,18,20]

可以给这个comprehension加个限制条件:

Prelude> [x*2 | x <- [1...10], x*2 > 12]
[14,16,18,20]

下面写一个函数,该函数使list中所有>10的奇数变为"BANG",小于10的奇数变为BOOM:

bangBoom xs = [if x > 10 then "BANG" else "BOOM" | x <- xs, odd x]

tip: odd函数判读x是否是奇数,如果是则返回True

还可以从多个list中取元素:

[x*y | x <- [1,2,3], y <- [4,5,6]]
[4,5,6,8,10,12,12,15,18]

实现自己的length函数:

length' xs = sum [1 | _ <- xs]

_表示我们不会用到这个值
操作含有 List 的 List

Prelude> let xxs = [[1,3,5,2,3,1,2,4,5],[1,2,3,4,5,6,7,8,9],[1,2,4,2,1,6,3,1,3,2,3,6]]
Prelude> [ [ x | x <- xs, even x ] | xs <- xxs]
[[2,2,4],[2,4,6,8],[2,4,2,6,2,6]]

Tuple

(1,2)      (True, "a", 1)

Tuple List:

[(1,2),(3,4),(5,6)]

但是[(1,2),(3,4,5),(5,6)]是会报错的,因为元素类型不一致
两个元素的Tuple可以称为序对(Pair) Tuple不能是单元素的,因为没有意义

操作函数

  • fst 返回序对的首项(只能操作序对,不能操作三元组等其他数量的Tuple)
  • snd 返回序对的尾项
Prudule> fst (1,2,[1,2,3])
1
Prudule> snd (1,2,[1,2,3])
[1,2,3]
  • zip 将两个list交叉配对生成一组Pair
Prudule> zip [1 .. 5] ["one", "two", "three", "four", "five"]
[(1,"one"),(2,"two"),(3,"three"),(4,"four"),(5,"five")]
Prudule> zip [5,3,2,6,2,7,2,5,4,6,6] ["im","a","turtle"]
[(5,"im"),(3,"a"),(2,"turtle")]

若是两个不同长度的 List,较长的那个会在中间断开,去匹配较短的那个

...

阅读全文 »

ghci中的一些命令与case 【持续更新...】

发布于
分类 Haskell
标签 Haskell

ghic中模式匹配

按照rwh书中模式匹配一节中sumList的例子在ghci敲出这样的代码:

Prelude> sumList (x:xs) = x + sumList xs
Prelude> sumList [] = 0

调用这个函数时是会报一个错误的:

Prelude> sumList [1,2,3]
*** Exception: <interactive>:2:1-14: Non-exhaustive patterns in function sumList

而实际如何要在ghci中做一个模式匹配函数的话应该这样写:

Prelude> let { sumList' [] = 0; sumList' (x:xs) = x + sumList' xs }
Prelude> sumList' [1,2,3]
6

ghci中切换工作目录与查看当前工作目录

Prelude> :cd /tmp/
Prelude> :show paths
current working directory: 
  /tmp
module import search paths:
  .

使用:cd 命令切换到指定目录
使用:show paths目录查看当前工作目录

...

阅读全文 »

Haskell 自定义type与typeclass

发布于
分类 Haskell
标签 Haskell

前言

在看《Haskell趣学指南》这本书的Build Our Own Type and Typeclass一章时,不是很好理解,这里结合《Real World Haskell》这本书做一下记录。

自定义type

Part One

Haskell中使用data关键字来定义新的数据类型:

data BookInfo = Book Int String [String] deriving (Show)

那么如何解读上面的表达式呢? 首先data关键字后边的BookInfo是新类型的名字,我们称BookInfo为类型构造器。类型构造器用于指代(refer)类型。类型名字的首字母必须大写,因此类型构造器的首字母也必须大写。 接下来的Book是值构造器(或者称:数据构造器)的名字,类型的值就是由值构造器创建的。 Book之后的Int String [String] 是类型的组成部分 在这个例子中,Int表示书ID, String表示书名,[String]表示作者

上面的描述其实很像OOP中的累的构造方法,BookInfo部分类似于OOP中的class,上文中的值构造器类似于class的构造方法,Book可以认为是构造方法的方法名,java等一些语言中构造方法是与class是同名的,但是Haskell中很明显没有这种约束,Haskell中类型构造器和值构造器的命名是独立的, 所以其实值构造器是可以与类型构造器同名的,即上面的例子可以写成:data BookInfo = BookInfo Int String [String]

可以将值构造器看作是一个函数:它创建并返回某个类型的值。下面的例子中我们将Int String [String] 三个类型的值应用到Book, 从而创建一个BookInfo类型的值

csapp = Book 123456 "Computer Systems: A Programmer's Perspective" ["Randal E.Bryant", "David R.O'Hallaron"] 

使用 :info 命令查看更多关于给定表达式的信息

:info BookInfo

类型别名

上面BookInfo类型的例子中,Int String [String] 一眼看不出来这三个成分是干什么用的,通过类型别名可以解决这个问题:

type BookId Int
type BookName String
data BookInfo = Book BookId BookName [String]

这样是不是一目了然了呢。

跟golang中的type关键字或者c/c++中的typedef 很像

类型别名也可以有参数

type AssocList k v = [(k,v)]
type IntMap v = Map Int v
type IntMap = Map Int

algebraic data type

Bool类型是代数数据类型的一个典型代表,一个代数类型可以有多个值构造器

data Bool = False| True

以此为例我们可以说Bool类型由True值或False值构成 下面是《Haskell趣学指南》中的例子:

data Shape = Circle Float Float Float | Rectangle Float Float Float Float

意思是图形可以是圆形或者是长方形

Record Syntax

比较好理解暂不做过多说明,后续再补坑

Type parameters

列表类型是多态的:列表中的元素可以是任何类型。我们也可以给自定义的类型添加多态性。只要在类型定义中使用类型变量就可以做到这一点。Prelude 中定义了一种叫做Mayb的类型:它用来表示这样一种值——既可以有值也可能空缺,比如数据库中某行的某字段就可能为空。

data Maybe a = Nothing | Just a         -- Defined in ‘GHC.Maybe’

递归定义

一个代数数据类型的值构造器可以有多个field,我们能够定义一个类型,其中他的值构造器的field就是他自己,这样我们可以递归的定义下去。我们可以这样定义我们的List:

data List a = Empty | Cons a (List a) deriving(Show,Read,Ord) 

用record syntax表示:

data List a = Empty | Cons {headList::a, tailList::List a} deriving(Show,Read,Ord) 
ghci> Empty
Empty
ghci> 5 `Cons` Empty
Cons 5 Empty
ghci> 4 `Cons` (5 `Cons` Empty)
Cons 4 (Cons 5 Empty)
ghci> 3 `Cons` (4 `Cons` (5 `Cons` Empty))
Cons 3 (Cons 4 (Cons 5 Empty))

我们可以只用特殊字符来定义函数,这样他们就会自动拥有中缀的性质,同样的我们可以套用在值构造器上,因为他们不过是回传类型的函数而已

infixr 5 :-:
data List a = Empty | a :-: (List a) deriving (Show, Eq, Read, Ord)

定义函数成operator时能够同时指定fixity(不是必须的)。fixity指定了他应该是left-associative还是right-associative,还有他的优先级。infixr是右结合,infixl是左结合,infix无左右优先性。优先级0-9。例:*的fixity是infixl 7,+的fixity是infixl 6, infixl代表他们都是left-associative,但是*的优先级大于+。

这样我们就可以这样写:

ghci> 3 :-: 4 :-: 5 :-: Empty
(:-:) 3 ((:-:) 4 ((:-:) 5 Empty))
ghci> let a = 3 :-: 4 :-: 5 :-: Empty
ghci> 100 :-: a
(:-:) 100 ((:-:) 3 ((:-:) 4 ((:-:) 5 Empty)))

haskell在deriving Show的时候仍然会视值构造器为前缀函数,因此要用括号括起来

构造自己的Typeclass

首先看一下Eq是怎么被定义的:

class Eq a Where
    (==) :: a->a->Bool
    (/=) :: a->a->Bool
    x == y = not (x /= y)
    x /= y = not (x == y)

tip: 上面的代码是书中给出的而在ghci中打印出来实际是下面这样的:

Prelude> :info Eq
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool
  {-# MINIMAL (==) | (/=) #-}
        -- Defined in ‘GHC.Classes’
instance Eq a => Eq [a] -- Defined in ‘GHC.Classes’
instance Eq Word -- Defined in ‘GHC.Classes’
instance Eq Ordering -- Defined in ‘GHC.Classes’
...

解释下:class Eq a where代表我们定义了一个typeclass叫做Eq,a是一个类型变量,他代表任何我们在定义instance时的类型,接下来我们定义了几个函数,不一定要实现函数但一定要写出函数的类型声明。

下面看下这个类型:

data TrafficLight = Red | Yellow | Green

这里定义了一个红绿灯的类型,该类型目前还不是任何class的instance。虽然通过derive可以让它成为Eq或者Show的instance,但在这里我们手动实现:

instance Eq TrafficLight where
    Red == Red = True
    Green == Green = True
    Yellow == Yellow = True
    _ == _ = False

instance关键字用来说明我们定义某个typeclass的instance。

由于==使用/=来定义的,同样/=使用==定义的,所以我们只要在instance中复写其中一个就好了。我们这样叫做定义了一个minimail complete difinition。这是说能让类型符合class行为所最小实现的函数数量。而Eq的minimal complete difinition需要==或者/=实现其中一个。而如果Eq这样定义:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

当我们定义instance时就需要实现两个函数。所以minimal complete difinition就是==和/=。

我们再来写Show的instance,要满足Show的minimal complete difinition需要实现show函数,它接收一个值返回一个字符串

instance Eq TrafficLight where
    show Red = "Red light"
    show Yellow = "Yellow light"
    show Green = "Green ligth"

subclass

可以把typeclass定义成其他typeclass的subclass,Num的class声明就有点长:

class (Eq a) => Num a where
    ...

我们可以在很多地方加上类型约束,这里就是在class Num where 中的a上加上它必须是Eq instance的约束。其实这可以理解为在定义Num这个class时,必须先定义他为Eq的instance。

泛型instance

Maybe或者List这种与TrafficLight不同,Maybe是一个泛型。它接收一个类型参数(像是Int)从而构造出一个具体的类型。从Eq的typeclass的声明中可以看到a必须是一个具体的类型,而Maybe不是一个具体的类型我们不能写成这样:

instance Eq Maybe where
    ...

下面的代码虽然Maybe m 是一个具体的类型但是还有一个问题,那就是无法保证Maybe装的东西可以是Eq

instance Eq (Maybe m) where
    Just x == Just y = x == y
    Nothing == Nothing = True
    _ == _ = False

所以还应该加上一个类型约束:

instance (Eq m) => Eq (Maybe m) where
    Just x == Just y = x == y
    Nothing == Nothing = True
    _ == _ = False

大部分情况下class声明中的类型约束都是要让一个typeclass成为另一个typeclass的subclass。而在 instance 宣告中的 class constraint 则是要表达型别的要求限制。

如果想看一个 typeclass 有定义哪些 instance。可以在 ghci 中输入 :info YourTypeClass。所以输入 :info Num 会告诉你这个 typeclass 定义了哪些函数,还有哪些类型属于这个 typeclass。:info 也可以查找类型跟类型构造器的信息。如果你输入 :info Maybe。他会显示 Maybe 所属的所有 typeclass。:info 也能告诉函数的型别宣告。

Functor typeclass

首先看下Functor这个typeclass

class Functor f where
    fmap :: (a -> b) -> f a -> f b

tip: ghci 8.8.1中打印结果如下:

Prelude> :info Functor
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b
  (<$) :: a -> f b -> f a
  {-# MINIMAL fmap #-}
        -- Defined in ‘GHC.Base’
instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor [] -- Defined in ‘GHC.Base’
instance Functor Maybe -- 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’

可以看到typeclass中的类型变量f并不是一个具体的类型,而是类似于Maybe这样的泛型。从上面我们可以看到fmap接收一个从a类型映射到b类型的函数和一个装有a类型值的functor,返回一个装有b类型值的functor

看下学list时学到的map函数:

Prelude> :t map
map :: (a -> b) -> [a] -> [b]

它接收一个从a类型映射为b类型的函数,和一个装有a类型值的List返回一个装有b类型值的List

是不是很像fmap,不错,List正是一个Functor的instance,而map就是fmap的实现(这一点看下ghci中:info Functor的打印结果就能确认)。

同样的Maybe也是Functor的一个instance:

instance Functor Maybe where
    fmap f (Just x) = Just (f x)
    fmap f Nothing = Nothing

看到这不免有些疑问,为什么上面instance Eq Maybe where不行在这里写成instance Functor Maybe where就行了呢?原因是Functor要接收的是一个泛型,而不是一个具体的类型。如果把f替换成Maybe,fmap就像是这样:(a -> b) -> Maybe a -> Maybe b,如果像上面将Eq时一样将f替换成Maybe m的话就会成这个样子了:(a -> b) -> Maybe m a -> Maybe m b 这显然是不对的。

如果一个泛型是接收两个参数的呢,以Either a b为例,可以这样写:

instance Functor (Either a) where
    fmap f (Right x) = Right (f x)
    fmap f (Left x) = Left x

就是把Either a作为Functor的一个instance(Either不能作为Functor的instance)

Kind

泛型(型别构造子)接收其他类型作为它的参数来构造出一个具体的类型。这有点像函数,也是接收一个值作为参数并回传另一个值。对于类型如何被套用到泛型上,我们看下正式的定义。
像是3,"abc"或者是takeWhile的值都有自己的类型(函数也是值的一种)。类型是一个标签,值会把它带着,这样我们就能推导出它的性质。但类型也有自己的标签,叫做kind,kind是类型的类型。

我们可以在ghci中通过:k来获取一个类型的kind:

Prelude> :k Int
Int :: *

*代表这个类型是具体类型。一个具体类型是没有任何类型参数的,值只能属于具体类型。*的读法叫做star或是type。

我们再看下Maybe的kind:

Prelude> :k Maybe
Maybe :: * -> *

可以看到Maybe的类型构造子接收一个具体类型(像是Int)然后返回一个具体类型。就像Int -> Int代表这个函数接收Int并返回Int。* -> *代表这个类型构造子接收一个具体类型并返回一个具体类型。我们再对Maybe套用类型参数后再看看它的kind:

Prelude> :k Maybe Int
Maybe Int :: *

...

阅读全文 »

centos搭建git服务器

发布于
分类 linux
标签 linux
标签 git

一、安装

sudo yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel
sudo yum install gcc perl-ExtUtils-MakeMaker
  1. 安装
sudo wget https://github.com/git/git/archive/v2.9.2.tar.gz
sudo tar -zvxf v2.9.2.tar.gz
cd git-2.9.2
sudo make prefix=/usr/local/git all
sudo make prefix=/usr/local/git install
  1. 将git设置为默认路径,不然后面克隆时会报错
sudo ln -s /usr/local/git/bin/git-upload-pack /usr/bin/git-upload-pack
sudo ln -s /usr/local/git/bin/git-receive-pack /usr/bin/git-receive-pack
  1. 添加git用户和用户组用来运行git服务
sudo groupadd git
sudo useradd git -g git
sudo passwd git
su - git

二、创建证书登录

cd /home/git
mkdir .ssh
chmod 700 .ssh
touch .ssh/authorized_keys
chmod 600 .ssh/authorized_keys
将公钥导入到authorized_keys
  1. 初始化Git仓库
cd /data
mkdir gitrepo
chown git:git gitrepo/
cd gitrepo
git init --bare starins.git
chown -R git:git starins.git

三、使用

git clone ssh://git@ip:port/data/gitrepo/starins.git

...

阅读全文 »