Abell Studio

这世界没有一件事情是虚空而生的。站在光里,背后就会有阴影,这深夜一片寂静,是因为你还没有听见声音。

Haskell Type与Typeclass

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 基础

第一个函数

创建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 【持续更新...】

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趣学指南》这本书的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服务器

一、安装

  1. sudo yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel sudo yum install gcc perl-ExtUtils-MakeMaker

  2. 安装 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

  3. 将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

  4. 添加git用户和用户组用来运行git服务

    sudo groupadd git
    sudo useradd git -g git
    sudo passwd git
    su - git
    

    二、创建证书登录

  5. cd /home/git
    mkdir .ssh
    chmod 700 .ssh
    touch .ssh/authorized_keys
    chmod 600 .ssh/authorized_keys
    将公钥导入到authorized_keys
    
  6. 初始化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

阅读全文 »