haskell学习笔记——用右结合定义左结合
前言:用右结合定义左结合真是非常烧脑啊,但是想通之后却给人一种unbelievable的感觉,惊叹于currying和high-ordered function的妙用
我们先考虑一个非常经典的问题:用foldr定义foldl
虽然很经典,但是很不简单
先放代码
myFoldl :: (a -> b -> a) -> a -> [b] -> a
myFoldl f z xs = foldr step id xs z
where step x g z = g (f z x)
我们前面foldr step id xs的部分实际上是得到了一个(a -> a)的函数,然后将它作用到我们的初值z上,以此实现用右结合定义左结合
我们考虑把右结合改成左结合实质上是在干嘛,事实上就是我们在左边添加一个数的时候,我们期望这个数和右边的第一个数先结合,然后再类似地传入后续的运算之中
于是我们首先便考虑把右边的计算结果变成一个函数,让它能够接受新加的一个参数并带入后边的运算
我们构造函数的过程便类似于递归了
我们把step x g z用lambda表达式稍微改一下
step x g = (\z -> g (f z x))
其中g是一个(a -> a)的函数,表示传入一个值以对最左边的数进行修改,再带入到后面的运算当中
那么新的g势必是传入一个新的数对第一个数进行修饰以后,得到一个新的函数再传入老的g进行计算
它的初值显然是id,表示只有一个数,那就返回它本身
然后接下来我不停把id中的第一个需要传入的数z用新的需要传入的数z'和当前得到的数x,变成 f z' x
这样就实现了把右结合变成左结合的过程
下面考虑我在Previous Part里留下的一个问题
我们先考虑右结合定义左结合的实质
其实本质上我们每次右结合之后就会传回一个函数,然后我们传入下一个参数就是利用这个参数对右结合的函数中的待传入函数处理的第一个参数进行处理,然后实现对函数的更新
由于前面我们是不断结合f,所以只需要传入一个参数对第一个参数进行处理,所以函数类型是(a -> a)
但在这里我们可能的结合有"*"和"/",或者是"+"和"-",这使得一个参数无法完成我们的需求
我们要传入一个(Int -> Int)的函数进行处理
所以我们返回的函数的传入值就是要对第一个参数进行的处理
我们每次在左边加一个数,实际上是利用这个数对右边的待传入函数处理的第一个参数进行处理,不停地把我们处理的东西例如f' a - 传给原来的g把g中原来待处理的参数出来了,然后把新的东西作为新的待传入函数处理的第一个参数
具体实现为 return (\f -> g ((f a) -))
下面给出完整实现的代码
import Prelude hiding (Maybe (..))
import Control.Monad
import Control.Applicative
import Data.Char
newtype Parser a = P { parse :: String -> [(a,String)] }
eval :: String -> Int
eval = fst . head . parse expr
item :: Parser Char
item = P (\program -> case program of
[] -> []
(x : xs) -> [(x, xs)])
instance Functor Parser where
fmap g p = P (\program -> case parse p program of
[] -> []
[(v, out)] -> [(g v, out)])
instance Applicative Parser where
pure v = P (\program -> [(v, program)])
pg <*> px = P (\program -> case parse pg program of
[] -> []
[(g, out)] -> parse (fmap g px) out)
instance Monad Parser where
p >>= f = P (\program -> case parse p program of
[] -> []
[(v, out)] -> parse (f v) out)
--many : perform arbitrary times
--some : perform at least 1 time
instance Alternative Parser where
empty = P (\program -> [])
p <|> q = P (\program -> case parse p program of
[] -> parse q program
rst -> rst)
sat :: (Char -> Bool) -> Parser Char
sat p = do x <- item
if p x then return x else empty
digit :: Parser Char
digit = sat isDigit
char :: Char -> Parser Char
char x = sat (x ==)
string :: String -> Parser String
string [] = return []
string (x : xs) = do char x
string xs
return (x : xs)
nat :: Parser Int
nat = do x <- some digit
return (read x)
int :: Parser Int
int = do char '-'
n <- nat
return (-n)
<|> nat
space :: Parser ()
space = do many (sat isSpace)
return ()
token :: Parser a -> Parser a
token v = do space
x <- v
space
return x
natural :: Parser Int
natural = token nat
integer :: Parser Int
integer = token int
symbol :: String -> Parser String
symbol xs = token (string xs)
nats :: Parser [Int]
nats = do symbol "["
n <- natural
ns <- many (do {symbol ","; natural})
symbol "]"
return (n : ns)
expr' :: Parser ((Int -> Int) -> Int)
expr' = do
a' <- terms'
let a = a' id
do
symbol "+"
b <- expr'
return (\f -> b ((f a) +))
<|> do
symbol "-"
b <- expr'
return (\f -> b ((f a) -))
<|>
return (\f -> f a)
expr :: Parser Int
expr = do
a <- expr'
return (a id)
terms' :: Parser ((Int -> Int) -> Int)
terms' = do
a <- factor
do
symbol "*"
b <- terms'
return (\f -> b ((f a) *))
<|> do
symbol "/"
b <- terms'
return (\f -> b ((f a) `div`))
<|> return (\f -> f a)
factor :: Parser Int
factor = do
symbol "("
g <- expr'
symbol ")"
return (g id)
<|> nat