haskell学习笔记——从零开始的整理
前言:下周就要半期考试啦,但这门语言的美和深刻的意义我也才在最近感受到,难道这就是压力带给人的buff吗hhh。值此机会,我从头到尾翻了一遍Graham Hutton的 《Programming in Haskell》,并在这里对书中“Basic Concepts”部分中的关键思想整理一下
一个直观的感受是,lambda calculus这个东西确实在愈来愈多地影响着主流语言,比如在c++11后可以定义的函数类型,给程序设计带来了很多便利,确实是源于FP
然后也能感受到的一个点是,作为一门纯粹的FP语言,haskell里的很多高阶的概念确乎难以移植到主流中,例如FP的实质是对函数进行组合来实现复杂的操作,这确实是可行的,但很多时候在理解和实现层面上就显得颇为困难。
但这也不在我一个初学者能考虑的范畴内,目前我还是先感受这种思想的美,并将它落诸纸笔吧。
一些基础的类型定义和函数定义就略过不表
可以参考这篇文章进行入门学习 Link
依我的理解,haskell里的函数其实都是一元函数,传入一个参数,并将它映射到另一个参数上
而多元函数的实际意义,实则是传入一个参数,返回一个函数,然后再对新函数传入下一个参数......
以比较典型的两个函数为例
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f a [] = a
foldl f a (x : xs) = foldl f (f a x) xs
foldr :: (b -> a -> a) -> a -> [b] -> a
foldr f a [] = a
foldr f a (x : xs) = f x (foldr f a xs)
这里foldl表示传入一个f :: a -> b -> a(依次传入一个a类型和一个b类型返回一个a类型的函数),然后传入一个a类型的值和一个b类型的list,最后返回一个a类型的值作为结果
函数的实际实现就是把[b]这个list里面的数一个一个拿出来依次和a进行函数运算
foldr是同理的,读者自证不难
这个又自然引出currying和high-ordered function
这个非常的关键,是我们实现很多复杂操作的关键
我的理解是,这建立在我们的参数是逐步传入的,这使得我们函数的组合变得非常灵活,不过这是后话了
声明一个类型,可以使用type(相当于define,起别名),data(有点像一个多种类型的mixture,用pattern matching 定义),newtype(只有一种类型)
然后类型的声明里是允许递归的
然后是类型族type classes,我的理解是,这代表满足一些公共性质(实际是拥有公共函数)的一些type构成的集合
我们可以使用instance关键字在类型族里加入我们定义的新类型
通过定义对当前类型定义这个类型族里的公共函数来实现定义
instance class T a where
-- declare functions in T with type a
The countdown problem
一个综合性很强的问题
贴上我的代码
import System.Win32 (COORD(yPos))
data Op
= Add
| Sub
| Mul
| Div
| Exp
deriving Eq
data Expr
= Val Int
| App Op Expr Expr
deriving Eq
ln2 :: Float
ln2 = log 2.0
valid :: Op -> Int -> Int -> Bool
valid Add x y = x <= y && (x + y < 2 ^ 31)
valid Sub x y = x > y
valid Mul x y = x <= y && x /= 1 && y /= 1 && (x * y < 2 ^ 31)
valid Div x y = (x `mod` y == 0) && y /= 1
valid Exp x y = x > 1 && y > 1 && y < 31 && ((fromIntegral y :: Float) * log (fromIntegral x :: Float) < (31 :: Float) * ln2)
-- We note that x ^ y < 2 ^ 31 <=> y * lnx < 31 * ln2
-- We need to prune some possibilities
apply :: Op -> Int -> Int -> Int
apply Add x y = x + y
apply Sub x y = x - y
apply Mul x y = x * y
apply Div x y = x `div` y
apply Exp x y = x ^ y
split :: [a] -> [([a], [a])]
split [] = []
split [n] = []
split (x : xs) = ([x], xs) : [(x : ls, rs) | (ls, rs) <- split xs]
type Result = (Expr, Int)
findResults :: [Int] -> [Result]
findResults [] = []
findResults [n] = [(Val n, n) | n > 0]
findResults ns = [res | (ls, rs) <- split ns
, lres <- findResults ls
, rres <- findResults rs
, res <- fix lres rres]
fix :: Result -> Result -> [Result]
fix (lexpr, lnum) (rexpr, rnum) = [(App op lexpr rexpr, apply op lnum rnum) | op <- [Add, Sub, Mul, Div, Exp], valid op lnum rnum]
insert :: Int -> [Int] -> [[Int]]
insert y [] = [[y]]
insert y xs'@(x : xs) = (y : xs') : [x : ys | ys <- insert y xs]
enumerate :: [Int] -> [[Int]]
enumerate [] = [[]]
enumerate (x : xs) = [xs'' | xs' <- left, xs'' <- insert x xs'] ++ left
where left = enumerate xs
solutions :: [Int] -> Int -> [Expr]
solutions ns n = [expr | ns' <- enumerate ns, (expr, num) <- findResults ns', num == n]
-- to find nearest solutions
findMinDis :: [Int] -> Int -> Int
findMinDis ns n = minimum [abs (num-n) | ns' <- enumerate ns, (expr, num) <- findResults ns']
findNear :: [Int] -> Int -> [Expr]
findNear ns n = [expr | ns' <- enumerate ns, (expr, num) <- findResults ns', abs (num-n) == dis]
where dis = findMinDis ns n
findSolutions :: [Int] -> Int -> [Expr]
findSolutions ns n = if not (null sol) then sol
else findNear ns n
where sol = solutions ns n
instance Show Op where
show Add = "+"
show Sub = "-"
show Mul = "*"
show Div = "/"
show Exp = "^"
instance Show Expr where
showsPrec _ (Val n) = shows n
showsPrec p (App op x y)
= showParen (p > q)
$ showsPrec q x . showChar ' ' . shows op
. showChar ' ' . showsPrec (succ q) y
where q = case op of
Add -> 6; Sub -> 6
Mul -> 7; Div -> 7
Exp -> 8