Haskell杂记

目录

构造代数类型

Record Syntax

类型参数

构造类型类

自动派生

二三函数


haskell的一等公民是function(函数,体现形式就是method),还有一个核心的概念是type(类型),所以haskell是用方法联系着类型,据说一心想干翻范畴论。我觉得可以这样讲:haskell是“面向函数编程”,而函数的形参类型是haskell中的各种强类型,例如:Int这种代数类型(太多值,范围[-2^29 .. 2^29-1]),Bool这种代数类型(俩值,False 或 True),Num这种类型类(可以有代数类型对它进行实现)也可以,传参就传值(代数类型的值)。

构造代数类型

以下简称类型。 

data Bool = False | True

data关键字,=的左端标明类型的名称即Bool=的右端就是值构造子(Value Constructor),它们明确了该类型可能的值。|读作“或”,所以可以这样阅读该声明:Bool类型的值可以是True或False。类型名和值构造子的首字母必大写。

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

Circle的值构造子有三个参数,都是Float,Rectangle的值构造子有四个Float类型参数。值构造子就跟普通函数并无二致

ghci> :t Circle   
Circle :: Float -> Float -> Float -> Shape   
ghci> :t Rectangle   
Rectangle :: Float -> Float -> Float -> Float -> Shape

surface函数: 

surface :: Shape -> Float   
surface (Circle _ _ r) = pi * r ^ 2   
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)


ghci> surface $ Circle 10 20 10   
314.15927   
ghci> surface $ Rectangle 0 0 100 100   
10000.0

该函数取一个Shape值并返回一个Float值。写Circle -> Float是不可以的,因为Circle并非类型,真正的类型应该是Shape。这与不能写True->False的道理是一样的。再就是,我们使用的模式匹配针对的都是值构造子。之前我们匹配过[]False5,它们都是不包含参数的值构造子。

类型别名

type String = [Char]  --String类型是char数组类型的别名

type SimpleState s a = s -> (a, s) -- 这个SimpleState s a带参数类型的类型是匿名函数类型的别名

Record Syntax

data Person = Person String String Int Float String String deriving (Show)

ghci> let guy = Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"   
ghci> guy   
Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"

获取Person类型实例的各个参数值的函数: (很无聊)类似java中的get

firstName :: Person -> String   
firstName (Person firstname _ _ _ _ _) = firstname   

lastName :: Person -> String   
lastName (Person _ lastname _ _ _ _) = lastname   

age :: Person -> Int   
age (Person _ _ age _ _ _) = age   

height :: Person -> Float   
height (Person _ _ _ height _ _) = height   

phoneNumber :: Person -> String   
phoneNumber (Person _ _ _ _ number _) = number   

flavor :: Person -> String   
flavor (Person _ _ _ _ _ flavor) = flavor


ghci> let guy = Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"   
ghci> firstName guy   
"Buddy"   
ghci> height guy   
184.2   
ghci> flavor guy   
"Chocolate"

Record Syntax写法:(deriving 自动派生一节讲)

--Record Syntax式定义
data Person = Person { firstName :: String   
                     , lastName :: String   
                     , age :: Int   
                     , height :: Float   
                     , phoneNumber :: String   
                     , flavor :: String   
                     } deriving (Show)


--通过Record Syntax,haskell就自动生成了这些访问器函数:
--firstName,lastName,age,height,phoneNumber和flavor
ghci> :t flavor   
flavor :: Person -> String   
ghci> :t firstName   
firstName :: Person -> String

 另一个例子:

data Car = Car String String Int deriving (Show)

ghci> Car "Ford" "Mustang" 1967   
Car "Ford" "Mustang" 1967

--Record Syntax式构造Car的实例
data Car = Car {company :: String, model :: String, year :: Int} deriving (Show)

ghci> Car {company="Ford", model="Mustang", year=1967}   
Car {company = "Ford", model = "Mustang", year = 1967}

类型参数

data Maybe a = Nothing | Just a

a就是个类型参数,类似泛型。 如,Just 'a'的类型就是Maybe Char。

ghci> Just "Haha"   
Just "Haha"   
ghci> Just 84   
Just 84   
ghci> :t Just "Haha"   
Just "Haha" :: Maybe [Char]   
ghci> :t Just 84   
Just 84 :: (Num t) => Maybe t   -- “(Num t) =>” 对t的类型约束
ghci> :t Nothing   
Nothing :: Maybe a   
ghci> Just 10 :: Maybe Double   
Just 10.0

另一个例子:

data Car = Car { company :: String 
                 , model :: String 
                 , year :: Int 
                 } deriving (Show)

--改成:
data Car a b c = Car { company :: a 
                       , model :: b 
                       , year :: c 
                        } deriving (Show)

tellCar :: Car -> String 
tellCar (Car {company = c, model = m, year = y}) = "This " ++ c ++ " " ++ m ++ " was made in " ++ show y


ghci> let stang = Car {company="Ford", model="Mustang", year=1967}   
ghci> tellCar stang  "This Ford Mustang was made in 1967"

--another test
tellCar :: (Show a) => Car String String a -> String   
tellCar (Car {company = c, model = m, year = y}) = "This " ++ c ++ " " ++ m ++ " was made in " ++ show y


ghci> tellCar (Car "Ford" "Mustang" 1967)   
"This Ford Mustang was made in 1967"   
ghci> tellCar (Car "Ford" "Mustang" "nineteen sixty seven")   
"This Ford Mustang was made in \"nineteen sixty seven\""   
ghci> :t Car "Ford" "Mustang" 1967   
Car "Ford" "Mustang" 1967 :: (Num t) => Car [Char] [Char] t   
ghci> :t Car "Ford" "Mustang" "nineteen sixty seven"   
Car "Ford" "Mustang" "nineteen sixty seven" :: Car [Char] [Char] [Char]

构造类型类

类型类定义了一系列函数,这些函数对于不同数据类型(类型类的实例)使用不同的函数实现。

class BasicEq a where
    isEqual :: a -> a -> Bool

class 是关键字,a是类型参数,它是这个BasicEq 类型类的实例类型(instance type)。 以下代码将 Bool 类型作为 BasicEq 的实例类型,实现了 isEqual 函数:

instance BasicEq Bool where
    isEqual True  True  = True
    isEqual False False = True
    isEqual _     _     = False


-- 
data Bool = False | True

在 ghci 里验证这个程序:

*Main> isEqual True True
True

*Main> isEqual False True
False

 如果试图将不是 BasicEq 实例类型的值作为输入调用 isEqual 函数,那么就会引发错误:

*Main> isEqual "hello" "moto"

<interactive>:5:1:
    No instance for (BasicEq [Char])
          arising from a use of `isEqual'
    Possible fix: add an instance declaration for (BasicEq [Char])
    In the expression: isEqual "hello" "moto"
    In an equation for `it': it = isEqual "hello" "moto"

如下构造,只要实现这两个函数中的一个,就可以顺利使用这两个函数: 

class BasicEq a where
    isEqual :: a -> a -> Bool
    isEqual x y = not (isNotEqual x y)

    isNotEqual :: a -> a -> Bool
    isNotEqual x y = not (isEqual x y)

instance BasicEq Bool where
    isEqual False False = True
    isEqual True  True  = True
    isEqual _     _     = False


Prelude> :load BasicEq_3.hs
[1 of 1] Compiling Main             ( BasicEq_3.hs, interpreted )
Ok, modules loaded: Main.

*Main> isEqual True True
True

*Main> isEqual False False
True

*Main> isNotEqual False True
True

 内置的Eq 类型类的定义:

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

-- Minimal complete definition:
--     (==) or (/=)
x /= y     =  not (x == y)
x == y     =  not (x /= y)

Show内置类型学类定义了show方法,Read 内置类型学类定义了read方法,可用于(反)序列化:

Main> :type show
show :: Show a => a -> String

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

他俩的实例类型举例: 

Main> data Color = Red | Green | Blue;

Main> show Red

<interactive>:10:1:
    No instance for (Show Color)
        arising from a use of `show'
    Possible fix: add an instance declaration for (Show Color)
    In the expression: show Red
    In an equation for `it': it = show Red

Prelude> Red

<interactive>:5:1:
    No instance for (Show Color)
        arising from a use of `print'
    Possible fix: add an instance declaration for (Show Color)
    In a stmt of an interactive GHCi command: print it

-- Color类型实例化类型类Show
instance Show Color where
    show Red   = "Color 1: Red"
    show Green = "Color 2: Green"
    show Blue  = "Color 3: Blue"
Prelude> read "3"

<interactive>:5:1:
    Ambiguous type variable `a0' in the constraint:
          (Read a0) arising from a use of `read'
    Probable fix: add a type signature that fixes these type variable(s)
    In the expression: read "3"
    In an equation for `it': it = read "3"

Prelude> (read "3")::Int
3

Prelude> :type it
it :: Int

Prelude> (read "3")::Double
3.0

Prelude> :type it
it :: Double

自动派生

Haskell 编译器可以将类型派生(derivation)为 Read 、 Show 、 Bounded 、 Enum 、 Eq 和 Ord 这些内置类型类的实例。

接着上面的一些例子:

data Color = Red | Green | Blue
    deriving (Read, Show, Eq, Ord)

*Main> show Red
"Red"

*Main> (read "Red")::Color
Red

*Main> (read "[Red, Red, Blue]")::[Color]
[Red,Red,Blue]

*Main> Red == Red
True

*Main> Data.List.sort [Blue, Green, Blue, Red]
[Red,Green,Blue,Blue]

*Main> Red < Blue
True

这个例子也是接着上面的例子: 

data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)

所以才可以在ghci中显示:

ghci> Circle 10 20 5   
Circle 10.0 20.0 5.0   
ghci> Rectangle 50 230 60 90   
Rectangle 50.0 230.0 60.0 90.0

接上面的例子继续举例: 

data Point = Point Float Float deriving (Show)   
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

surface :: Shape -> Float   
surface (Circle _ r) = pi * r ^ 2   
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)

ghci> surface (Rectangle (Point 0 0) (Point 100 100))   
10000.0   
ghci> surface (Circle (Point 0 0) 24)   
1809.5574

另一个例子:

-- 记录语法构造Person这个数据类型
data Person = Person { firstName :: String   
                     , lastName :: String   
                     , age :: Int   
                     } deriving (Eq)


ghci> let mikeD = Person {firstName = "Michael", lastName = "Diamond", age = 43}   
ghci> let adRock = Person {firstName = "Adam", lastName = "Horovitz", age = 41}   
ghci> let mca = Person {firstName = "Adam", lastName = "Yauch", age = 44}   
ghci> mca == adRock   
False   
ghci> mikeD == adRock   
False   
ghci> mikeD == mikeD   
True   
ghci> mikeD == Person {firstName = "Michael", lastName = "Diamond", age = 43}   
True

Person如今已经成为了Eq的成员,我们就可以将其应用于所有在类型声明中用到Eq类约束的函数了,如elem。

ghci> let beastieBoys = [mca, adRock, mikeD]   
ghci> mikeD `elem` beastieBoys   
True

show和read可以(反)序列化一个类型实例。 

data Person = Person { firstName :: String   
                     , lastName :: String   
                     , age :: Int   
                     } deriving (Eq, Show, Read)


ghci> let mikeD = Person {firstName = "Michael", lastName = "Diamond", age = 43}   
ghci> mikeD   
Person {firstName = "Michael", lastName = "Diamond", age = 43}   
ghci> "mikeD is: " ++ show mikeD   
"mikeD is: Person {firstName = \"Michael\", lastName = \"Diamond\", age = 43}"

ghci> read "Person {firstName =\"Michael\", lastName =\"Diamond\", age = 43}" :: Person   
Person {firstName = "Michael", lastName = "Diamond", age = 43}

ghci> read "Person {firstName =\"Michael\", lastName =\"Diamond\", age = 43}" == mikeD   
True

 例子:

data Bool = False | True deriving (Ord)

ghci> True `compare` False   
GT   
ghci> True > False   
True   
ghci> True  
False

如果Maybe a数据类型也是类型类Ord的实例,则:

ghci> Nothing  
True   
ghci> Nothing > Just (-49999)   
False   
ghci> Just 3 `compare` Just 2   -- Just 3 or Just 2 :: (Num t) => Maybe t   
ghci> Just 100 > Just 50   -- 先比较Nothing 和 Just,再比较 t
True

haskell的类型总结:haskell有内置类型类,如:Eq,可以自定义类型类,如:文中;类型类可以派生出它的类型实例,就是一般称为数据类型,haskell有内置数据类型,如:Int,Bool,可以自定义数据类型,如:Person;数据类型有数据类型的实例,我们可以称它为类型的值,如:True或False。

或这样和java对比,有待商榷,理解有误还请指出。类型类与接口(抽象类),类型与父类(抽象类),值(类型的实例)与子类这样对应。

newtype,data,type

Haskell 提供我们另外一种方式来创建新类型,即采用 newtype 关键字。

-- file: ch06/Newtype.hs
data DataInt = D Int
    deriving (Eq, Ord, Show)

newtype NewtypeInt = N Int
    deriving (Eq, Ord, Show)


*Main> N 1 < N 2
True

*Main> N 313 + N 37

<interactive>:9:7:
    No instance for (Num NewtypeInt) arising from a use of ‘+’
    In the expression: N 313 + N 37
    In an equation for ‘it’: it = N 313 + N 37

 如果newtype不使用自动派生来公开基础类型(本例中Int)对类型类的实现(Num),我们可以自由地编写新实例类型去实现或保留类型类的未实现(data也可以这样公开) 。比起 data 关键字,它有更多的限制于其使用上。 说白了, newtype 只能有一个值构造子,并且那个构造子只有一个参数(field)。这使得在运行的时候,无论在时间还是空间上都更有效率。

-- file: ch06/NewtypeDiff.hs
-- 可以:任意数量的构造器和字段(这里的两个Int为两个字段(fields))
data TwoFields = TwoFields Int Int

-- 可以:恰一个字段
newtype Okay = ExactlyOne Int

-- 可以:类型变量是没问题的
newtype Param a b = Param (Either a b)

-- 可以:记录语法是友好的
newtype Record = Record {
        getInt :: Int
    }

-- 不可以:没有字段
newtype TooFew = TooFew

-- 不可以:多于一个字段
newtype TooManyFields = Fields Int Int

-- 不可以:多于一个构造器
newtype TooManyCtors = Bad Int
                     | Worse Int

为了理解不同点,看:

--如果在运行时 undefined 被求值会导致崩溃
Prelude> undefined
*** Exception: Prelude.undefined

-- 模式匹配 DataInt类型的 D Int 值构造子 ,构造子也是函数
*Main> case (D undefined) of D _ -> 1
1

*Main> case undefined of D _ -> 1
*** Exception: Prelude.undefined

-- 模式匹配 NewtypeInt 类型的 N Int 值构造子 ,构造子也是函数
*Main> case (N undefined) of N _ -> 1
1

*Main> case undefined of N _ -> 1
1

没有崩溃!因为newtype的值构造子,只用于编译时,在运行时是不存在的。在运行时,已经没有NewtypeInt类型的值构造器N了。
对“N _”进行匹配,事实上就是对“_”进行匹配。而“_”总是会满足匹配条件,所以undefined是不需要被求值的。

当我们在表达式中应用N Int构造子时,就我们和编译器而言,我们意愿是强制表达式类型从Int类型转为NewtypeInt类型,但在运行时绝对不会发生任何事情。类似地,当我们在模式中匹配N Int构造子时,我们强制表达式从NewtypeInt类型转到Int,但同样在运行时不涉及任何开销。

再总结:命名类型的三种方式 这里简要回顾一下Haskell为类型引入新名称的三种方法。1.data关键字引入了一种真正新的代数数据类型。 2. type关键字为我们提供了用于现有类型的同义词,我们可以互换地使用该类型及其同义词。 3.newtype关键字为现有类型提供了不同的标识,基础原始类型和新类型不可互换。

二三函数

shiftL 函数实现逻辑左移, (.&.) 实现二进制位的并操作, (.|.) 实现二进制位的或操作, ord 函数则返回给定字符对应的编码值。

-- file: ch04/Adler32.hs

import Data.Char (ord)
import Data.Bits (shiftL, (.&.), (.|.))

base = 65521

adler32 xs = helper 1 0 xs
    where helper a b (x:xs) = let a' = (a + (ord x .&. 0xff)) `mod` base
                                  b' = (a' + b) `mod` base
                              in helper a' b' xs
          helper a b []     = (b `shiftL` 16) .|. a

-- code by java
{-public class Adler32
  {
    private static final int base = 65521;

    public static int compute(byte[] data, int offset, int length)
    {
        int a = 1, b = 0;

        for (int i = offset; i < offset + length; i++) {
            a = (a + (data[i] & oxff)) % base
            b = (a + b) % base;
        }

        return (b << 16) | a;
    }
  }
-}

传入参数的数量,少于函数所能接受参数的数量,这种情况被称为函数的部分应用(partial application of the function)函数.

Prelude> :type zip3
zip3 :: [a] -> [b] -> [c] -> [(a, b, c)]

Prelude> :type zip3 "foo"
zip3 "foo" :: [b] -> [c] -> [(Char, b, c)]

Prelude> :type zip3 "foo" "bar"
zip3 "foo" "bar" :: [c] -> [(Char, Char, c)]

Prelude> :type zip3 "foo" "bar" "quux"
zip3 "foo" "bar" "quux" :: [(Char, Char, Char)]

在上面的例子中, zip3 "foo" 就是一个部分函数,它以 "foo" 作为第一个参数,部分应用了 zip3 函数;而 zip3 "foo""bar" 也是另一个部分函数,它以 "foo" 和 "bar" 作为参数,部分应用了 zip3 函数。

只要给部分函数补充上足够的参数,它就可以被成功求值:

Prelude> let zip3foo = zip3 "foo"

Prelude> zip3foo "bar" "quux"
[('f','b','q'),('o','a','u'),('o','r','u')]

Prelude> let zip3foobar = zip3 "foo" "bar"

Prelude> zip3foobar "quux"
[('f','b','q'),('o','a','u'),('o','r','u')]

Prelude> zip3foobar [1, 2, 3]
[('f','b',1),('o','a',2),('o','r',3)]

 部分函数的应用被称为柯里化(currying).

-- file: ch04/niceSum.hs
niceSum :: [Integer] -> Integer
niceSum xs = foldl (+) 0 xs

实际上,并不需要完全应用 foldl [译注:完全应用是指提供函数所需的全部参数],niceSum 函数的 xs 参数,以及传给 foldl函数的 xs 参数,这两者都可以被省略,最终得到一个更紧凑的函数,它的类型也和原本的一样:

-- file: ch04/niceSumPartial.hs
niceSumPartial :: [Integer] -> Integer
niceSumPartial = foldl (+) 0


Prelude> :load niceSumPartial.hs
[1 of 1] Compiling Main             ( niceSumPartial.hs, interpreted )
Ok, modules loaded: Main.

*Main> niceSumPartial [1 .. 10]
55

 Haskell 提供了一种方便的符号快捷方式,用于对中序函数进行部分应用:使用括号包围一个操作符,通过在括号里面提供左操作对象或者右操作对象,可以产生一个部分应用函数。这种类型的部分函数应用称之为节(section)。

Prelude> (1+) 2
3

Prelude> map (*3) [24, 36]
[72,108]

Prelude> map (2^) [3, 5, 7, 9]
[8,32,128,512]

之前提到过,通过使用反括号来包围一个函数,可以将这个函数用作中序操作符。这种用法可以让节使用函数:

Prelude> :type (`elem` ['a' .. 'z'])
(`elem` ['a' .. 'z']) :: Char -> Bool

Prelude> (`elem` ['a' .. 'z']) 'f'
True

Prelude> (`elem` ['a' .. 'z']) '1'
False

代数数据类型的访问器函数:

data T a b = MkT { getA :: a, getB :: b }

上面的代码会自动定义2个辅助函数 :

getA :: (T a b) -> a
getA (MkT x _) = x

getB :: (T a b) -> b
getB (MkT _ y) = y
data ParseState = ParseState {
      string :: L.ByteString
    , offset :: Int64           -- imported from Data.Int
    } deriving (Show)

newtype Parse a = Parse {
    runParse :: ParseState -> Either String (a, ParseState)
    }


-- 上面的代码会自动定义辅助函数 :
runParse :: (Parse a) -> ParseState -> Either String (a, ParseState)
runParse (Parse a) = ParseState -> Either String (a, ParseState)

Case 表达式:

fromMaybe defval wrapped =
    case wrapped of
      Nothing     -> defval
      Just value  -> value

case 关键字后面可以跟任意表达式,这个表达式的结果即是模式匹配的目标。of 关键字标识着表达式到此结束,以及匹配区块的开始,这个区块将用来定义每种模式及其对应的表达式。

该区块的每一项由三个部分组成:一个模式,接着一个箭头 ->,接着一个表达式;如果这个模式匹配中了,则计算并返回这个表达式的结果。这些表达式应当是同一类型的。第一个被匹配中的模式对应的表达式的计算结果即是 case 表达式的结果。匹配按自上而下的优先级进行。

没有参数的函数:

getState :: Parse ParseState
getState = Parse (\s -> Right (s, s))

five' :: () -> Int
five' () = 5

five :: Int
five = 5

{- 
    void scream() {
      printf("Aaaah!\n");
    }
-}
scream :: IO()
scream = putStrLn "Aaaah!"

<$> 是 fmap(Functor类型类的函数) 的中缀表达式版本。 

记外

我们来看一下范畴论在现代数学中的排位:

参考:https://rwh.readthedocs.io/en/latest/chp/6.html

https://www.kancloud.cn/kancloud/learnyouahaskell/44652

发布了51 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/liao_hb/article/details/90315189