Featured image of post haskell book

haskell book

the journal of reading Haskell Programming from first principles

Basic Datatypes

In Haskell, there are seven categories of entities that have names: functions, term-level variables, data constructors, type variables, type constructors, type classes, and modules.

term-level variables

for example, greet and name/greeting in scope

1
2
3
4
5
greet :: [Char]
greet =
  let name = "Alice"
      greeting = "Hello, " ++ name
   in greeting

type variables

for example: a, b below

1
map :: (a -> b) -> [a] -> [b]

type constructor

type constructor, 类型构造器, 确实非常生动. for example, data constructor: (1, 2) :: (Int, Int) no equals to (2, 2) :: (Int, Int). the same as type constructor: (Double, Int) no equals to (Double, Double).

what writting in the type signature is type constructor. for example: fst :: (a, b) -> a is the type signature of fst. and the first type parameter of -> is (a, b), with returning

types

understanding the function type

the arror, ->, is a type constructor with no data constructors, although it takes arguments.

(->) a b resemble as (,) double, which means that Functions are values

type signature

Broadly speaking, type signatures may have three kinds of types: concrete, constrained polymorphic, or parametrically polymorphic.

In Haskell, polymorphism divides into two categories: parametric polymorphism and constrained polymorphism. If you’ve encountered polymorphism before, it was probably a form of constrained, often called ad-hoc, polymorphism. Ad-hoc polymorphism in Haskell is implemented with type classes.

If a variable represents a set of possible values, then a type vari-able represents a set of possible types. (用于说明, type variable resemble value variable)

type inference

Haskell’s type inference is built on an extended version of the Damas-Hindley-Milner type system.

type class

purely functional programming language

This property — being lambda calculus and nothing more is - what makes Haskell a purely functional programming language.

algebraic type

1
2
3
>>> data Doggies a = Husky a | Mastiff a deriving (Eq, Show)
>>> :k Doggies
Doggies :: * -> *
  • * 表示一个具体的类型, such as Int, Bool, String,
  • * -> * 表示一个类型构造器, 他需要一个类型参数才能变成具体类型. 这两个 * 不是同一个意思, 前一个 * 表示一个 concrete type; 后一个 * 应该是 Doggies *(这个 * 是前面传入的 concrete type)

* represent a concrete type, such as Int, Bool, String, etc. Not Maybe, but Maybe Int. 换句话来说: A type constant or a fully applied type has the kind *. 这里的 type constant 指的是 Int, Bool, etc. fully applied type 指的是 Maybe Int, etc.

1
2
3
>>> data HuskyType a = HUskyData
>>> :k HuskyType
HuskyType :: k -> *

k, here, is a kind variable, which could be any kind, called kind polymorphism.

1
2
>>> :k HuskyType Maybe
HuskyType Maybe :: *

cardinality(势)

  1. Sum ( | )
  2. Product ( struct )
  3. Exponential ( function )

解释一下: card(a -> b) = card(b) ^ card(a), 因为对于 a 中的每一个元素, 都有 card(b) 种选择.

注意一下: card(a -> b) /= card( (a, b) ), 因为映射实际上是双元组的集合.

building projects

It seems that stack is obsolete, using cabal instead. 感觉 haskell 添加一个包, 没 rust 方便, rust 直接 cargo add <package> 就行了.

applicative

Applicative can be thought of as characterizing monoidal functors in Haskell.

这句话应该这么理解

1
2
3
class Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

其中 pure id 是幺元. 而 <*> 是满足结合律的二元运算.

1
pure (.) <*> u <*> v <*> w === u <*> (v <*> w)

Monad

Reader

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
newtype Reader r a = Reader {runReader :: r -> a}

instance Functor (Reader r) where
  fmap f (Reader r) = Reader $ fmap f r

instance Applicative (Reader r) where
  pure x = Reader $ const x -- 这一定程度上是取悦类型检查器
  Reader rf <*> Reader ra = Reader $ \r -> rf r (ra r)

instance Monad (Reader r) where
  (Reader ra) >>= f = Reader $ \r -> runReader (f (ra r)) r

ask :: Reader r r
ask = Reader id

data ServerConfig = ServerConfig
  { port :: Int,
    host :: String
  }
  deriving (Show)

getConnectionString :: Reader ServerConfig String
getConnectionString = do
  config <- ask
  return $ host config ++ ":" ++ show (port config)

join :: (Monad m) => m (m a) -> m a
join mm = mm >>= id

getConnectionString' :: Reader ServerConfig String
getConnectionString' =
  join $
    fmap
      (\config -> Reader $ \r -> host config ++ ":" ++ show (port config) ++ show r)
      (Reader id)

getConnectionString1 :: Reader ServerConfig String
getConnectionString1 = Reader $ \config -> host config ++ ":" ++ show (port config)

getConnectionString2 :: Reader ServerConfig String
getConnectionString2 = do
  Reader $ \config -> host config ++ ":" ++ show (port config)

main :: IO ()
main = do
  let config = ServerConfig 8080 "localhost"
  let connStr = runReader getConnectionString config
  print connStr -- localhost:8080

解读:

  1. getConfigString 这里的 config <- ask + return $ + runReader r env 是 Reader 的常见用法
  2. 这里的 do desugar 后, 是 getConfigString', 不过将 >>= 换成了 join + fmap, 这里的 \r\config 都是传入的 env
  3. 但是这个例子展开以后, 发现有一些冗余的, 所以可以简化成 getConnectionString1

haskell by example

在看 haskell by example, 同时发现了一些有趣的教程.

gadt

quantified types

io-streams

string-format