在最近简要回顾了Haskell之后,对于monad本质上是什么,有什么简单、简洁、实用的解释?
我发现,我遇到的大多数解释都很难理解,而且缺乏实际细节。
在最近简要回顾了Haskell之后,对于monad本质上是什么,有什么简单、简洁、实用的解释?
我发现,我遇到的大多数解释都很难理解,而且缺乏实际细节。
当前回答
遵循您简短、简洁、实用的指示:
理解monad最简单的方法是在上下文中应用/组合函数。假设你有两个计算,它们都可以看作是两个数学函数f和g。
f取一个String并生成另一个String(取前两个字母)g获取一个String并生成另一个String(大写转换)
因此,在任何语言中,“取前两个字母并将其转换为大写”的转换都会写成g(f(“某个字符串”))。因此,在纯完美函数的世界中,合成只是:先做一件事,然后再做另一件事。
但假设我们生活在一个功能可能失败的世界中。例如:输入字符串可能有一个字符长,因此f将失败。所以在这种情况下
f获取一个String并生成一个String或Nothing。g仅在f未失败时生成字符串。否则,将不生成任何内容
所以现在,g(f(“somestring”))需要一些额外的检查:“计算f,如果它失败,那么g应该返回Nothing,否则计算g”
此思想可应用于任何参数化类型,如下所示:
让Context[Sometype]是Context中Sometype的计算。考虑功能
f: :AnyType->上下文[Sometype]g: :某些类型->上下文[AnyOtherType]
合成g(f())应该读作“compute f。在这个上下文中,做一些额外的计算,然后计算g,如果它在上下文中有意义”
其他回答
tl;博士
{-# LANGUAGE InstanceSigs #-}
newtype Id t = Id t
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
开场白
应用程序运算符$of函数
forall a b. a -> b
是规范定义的
($) :: (a -> b) -> a -> b
f $ x = f x
infixr 0 $
根据Haskell基函数应用f x(infixl 10)。
作文定义为$as
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x
infixr 9 .
并且满足所有f g h的等价性。
f . id = f :: c -> d Right identity
id . g = g :: b -> c Left identity
(f . g) . h = f . (g . h) :: a -> d Associativity
.是关联的,id是它的右标识和左标识。
克莱斯利三人组
在编程中,monad是带有monad类型类实例的函子类型构造函数。定义和实现有几个等价的变体,每个变体对monad抽象的直觉略有不同。
函子是带有函子类型类实例的*->*类型的类型构造函数f。
{-# LANGUAGE KindSignatures #-}
class Functor (f :: * -> *) where
map :: (a -> b) -> (f a -> f b)
除了遵循静态强制类型协议之外,函子类型类的实例必须遵守所有f g的代数函子定律。
map id = id :: f t -> f t Identity
map f . map g = map (f . g) :: f a -> f c Composition / short cut fusion
函数计算具有以下类型
forall f t. Functor f => f t
计算c r包含上下文c中的结果r。
一元一元函数或Kleisli箭头的类型为
forall m a b. Functor m => a -> m b
Kleisi箭头是接受一个参数a并返回一元计算m b的函数。
Monads是用Kleisli三重函数定义的
(m, return, (=<<))
实现为类型类
class Functor m => Monad m where
return :: t -> m t
(=<<) :: (a -> m b) -> m a -> m b
infixr 1 =<<
Kleisli标识返回是一个Kleisli箭头,它将值t提升为单元上下文m。
Kleisli组成<=<根据扩展定义为
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x
infixr 1 <=<
<=<组成两个Kleisli箭头,将左箭头应用于右箭头应用的结果。
monad类型类的实例必须遵守monad定律,这在Kleisli组合中最为优雅地表述为:forall f g h。
f <=< return = f :: c -> m d Right identity
return <=< g = g :: b -> m c Left identity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d Associativity
<=<是关联的,返回是它的右标识和左标识。
身份
标识类型
type Id t = t
是类型上的标识函数
Id :: * -> *
被解释为函子,
return :: t -> Id t
= id :: t -> t
(=<<) :: (a -> Id b) -> Id a -> Id b
= ($) :: (a -> b) -> a -> b
(<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
= (.) :: (b -> c) -> (a -> b) -> (a -> c)
在规范的Haskell中,定义了身份monad
newtype Id t = Id t
instance Functor Id where
map :: (a -> b) -> Id a -> Id b
map f (Id x) = Id (f x)
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
选项
选项类型
data Maybe t = Nothing | Just t
编码计算可能t不一定产生结果t,计算可能“失败”。选项monad已定义
instance Functor Maybe where
map :: (a -> b) -> (Maybe a -> Maybe b)
map f (Just x) = Just (f x)
map _ Nothing = Nothing
instance Monad Maybe where
return :: t -> Maybe t
return = Just
(=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
f =<< (Just x) = f x
_ =<< Nothing = Nothing
a->Maybe b仅在Maybe a产生结果时应用于结果。
newtype Nat = Nat Int
自然数可以编码为大于或等于零的整数。
toNat :: Int -> Maybe Nat
toNat i | i >= 0 = Just (Nat i)
| otherwise = Nothing
自然数在减法下不是封闭的。
(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)
infixl 6 -?
选项monad涵盖了异常处理的基本形式。
(-? 20) <=< toNat :: Int -> Maybe Nat
List
列表monad,覆盖列表类型
data [] t = [] | t : [t]
infixr 5 :
及其加法幺半群运算“append”
(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[] ++ ys = ys
infixr 5 ++
编码非线性计算[t],产生自然量0,1。。。结果t。
instance Functor [] where
map :: (a -> b) -> ([a] -> [b])
map f (x : xs) = f x : map f xs
map _ [] = []
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> [a] -> [b]
f =<< (x : xs) = f x ++ (f =<< xs)
_ =<< [] = []
Extension=<<将从Kleisli箭头a->[b]的应用f x到[a]的元素的所有列表[b]连接到一个结果列表[b]。
设正整数n的正除数为
divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]
divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)
then
forall n. let { f = f <=< divisors } in f n = []
在定义monad类型类时,Haskell标准使用其flip,即绑定运算符>>=,而不是extension=<<。
class Applicative m => Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(>>) :: forall a b. m a -> m b -> m b
m >> k = m >>= \ _ -> k
{-# INLINE (>>) #-}
return :: a -> m a
return = pure
为了简单起见,本解释使用了类型类层次结构
class Functor f
class Functor m => Monad m
在Haskell中,当前的标准层次结构是
class Functor f
class Functor p => Applicative p
class Applicative m => Monad m
因为不仅每个单子都是函子,而且每个应用格也是函子,每个单子也是应用格。
使用列表monad,命令式伪代码
for a in (1, ..., 10)
for b in (1, ..., 10)
p <- a * b
if even(p)
yield p
大致翻译为do块,
do a <- [1 .. 10]
b <- [1 .. 10]
let p = a * b
guard (even p)
return p
等效的monad理解,
[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]
和表达式
[1 .. 10] >>= (\ a ->
[1 .. 10] >>= (\ b ->
let p = a * b in
guard (even p) >> -- [ () | even p ] >>
return p
)
)
Do符号和monad理解是嵌套绑定表达式的语法糖。绑定运算符用于一元结果的本地名称绑定。
let x = v in e = (\ x -> e) $ v = v & (\ x -> e)
do { r <- m; c } = (\ r -> c) =<< m = m >>= (\ r -> c)
哪里
(&) :: a -> (a -> b) -> b
(&) = flip ($)
infixl 0 &
定义了防护功能
guard :: Additive m => Bool -> m ()
guard True = return ()
guard False = fail
其中单位类型或“空元组”
data () = ()
支持选择和失败的加法单子可以通过使用类型类抽象
class Monad m => Additive m where
fail :: m t
(<|>) :: m t -> m t -> m t
infixl 3 <|>
instance Additive Maybe where
fail = Nothing
Nothing <|> m = m
m <|> _ = m
instance Additive [] where
fail = []
(<|>) = (++)
其中fail和<|>形成所有k l m的幺半群。
k <|> fail = k
fail <|> l = l
(k <|> l) <|> m = k <|> (l <|> m)
失败的是吸收/消灭零元素的加法单体
_ =<< fail = fail
如果在
guard (even p) >> return p
即使p为真,则保护产生[()],并且根据>>的定义,产生局部常数函数
\ _ -> return p
应用于结果()。如果为false,则保护生成列表monad的fail([]),这不会产生要应用>>的Kleisli箭头的结果,因此跳过此p。
状态
不光彩的是,monad用于编码有状态计算。
状态处理器是一种功能
forall st t. st -> (t, st)
转换状态st并产生结果t。状态st可以是任何东西。没有,标志,计数,数组,句柄,机器,世界。
状态处理器的类型通常称为
type State st t = st -> (t, st)
状态处理器monad是kind*->*函子state st.Kleisli状态处理器mond的箭头是函数
forall st a b. a -> (State st) b
在规范的Haskell中,定义了状态处理器monad的惰性版本
newtype State st t = State { stateProc :: st -> (t, st) }
instance Functor (State st) where
map :: (a -> b) -> ((State st) a -> (State st) b)
map f (State p) = State $ \ s0 -> let (x, s1) = p s0
in (f x, s1)
instance Monad (State st) where
return :: t -> (State st) t
return x = State $ \ s -> (x, s)
(=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
in stateProc (f x) s1
状态处理器通过提供初始状态来运行:
run :: State st t -> st -> (t, st)
run = stateProc
eval :: State st t -> st -> t
eval = fst . run
exec :: State st t -> st -> st
exec = snd . run
状态访问由原语get和put提供,它们是对有状态monad的抽象方法:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Monad m => Stateful m st | m -> st where
get :: m st
put :: st -> m ()
m->st声明状态类型st对monad m的函数依赖性;例如,状态t将确定状态类型为t唯一。
instance Stateful (State st) st where
get :: State st st
get = State $ \ s -> (s, s)
put :: st -> State st ()
put s = State $ \ _ -> ((), s)
单位类型类似于C中的空隙。
modify :: Stateful m st => (st -> st) -> m ()
modify f = do
s <- get
put (f s)
gets :: Stateful m st => (st -> t) -> m t
gets f = do
s <- get
return (f s)
gets通常与记录字段访问器一起使用。
状态monad等价于变量线程
let s0 = 34
s1 = (+ 1) s0
n = (* 12) s1
s2 = (+ 7) s1
in (show n, s2)
其中s0::Int,是同样透明的,但更加优雅和实用
(flip run) 34
(do
modify (+ 1)
n <- gets (* 12)
modify (+ 7)
return (show n)
)
modify(+1)是一种类型为State Int()的计算,但其效果等同于return()。
(flip run) 34
(modify (+ 1) >>
gets (* 12) >>= (\ n ->
modify (+ 7) >>
return (show n)
)
)
结合性的单子定律可以用>>=forall m f g来表示。
(m >>= f) >>= g = m >>= (\ x -> f x >>= g)
or
do { do { do {
r1 <- do { x <- m; r0 <- m;
r0 <- m; = do { = r1 <- f r0;
f r0 r1 <- f x; g r1
}; g r1 }
g r1 }
} }
与面向表达式的编程(例如Rust)一样,块的最后一条语句表示其产量。绑定运算符有时被称为“可编程分号”。
对结构化命令式编程中的迭代控制结构原语进行单点仿真
for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())
while :: Monad m => m Bool -> m t -> m ()
while c m = do
b <- c
if b then m >> while c m
else return ()
forever :: Monad m => m t
forever m = m >> forever m
输入/输出
data World
I/O世界状态处理器monad是纯Haskell和真实世界的协调,是功能外延和命令式操作语义的协调。与实际严格执行情况类似:
type IO t = World -> (t, World)
不纯洁的原语促进了交互
getChar :: IO Char
putChar :: Char -> IO ()
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
hSetBuffering :: Handle -> BufferMode -> IO ()
hTell :: Handle -> IO Integer
. . . . . .
使用IO原语的代码的杂质由类型系统永久协议化。因为纯净是可怕的,在IO中发生的一切,都留在IO中。
unsafePerformIO :: IO t -> t
或者,至少应该。
Haskell程序的类型签名
main :: IO ()
main = putStrLn "Hello, World!"
扩展到
World -> ((), World)
改变世界的函数。
后记
对象是Haskell类型,态射是Haskelr类型之间的函数的类别是,“快速和松散”,类别是Hask。
函子T是从范畴C到范畴D的映射;对于C中的每个对象,D中的一个对象
Tobj : Obj(C) -> Obj(D)
f :: * -> *
对于C中的每个态射,D中的一个态射
Tmor : HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
map :: (a -> b) -> (f a -> f b)
其中X,Y是C中的对象。HomC(X,Y)是C中所有态射X->Y的同态类。
Tmor Tobj
T(id) = id : T(X) -> T(X) Identity
T(f) . T(g) = T(f . g) : T(X) -> T(Z) Composition
范畴C的Kleisli范畴由Kleisli三元组给出
<T, eta, _*>
内函子的
T : C -> C
(f) 、同一态射eta(return)和扩展运算符*(=<<)。
Hask中的每个Kleisli态射
f : X -> T(Y)
f :: a -> m b
由扩展运算符
(_)* : Hom(X, T(Y)) -> Hom(T(X), T(Y))
(=<<) :: (a -> m b) -> (m a -> m b)
在Hask的Kleisli范畴中给出了一个态射
f* : T(X) -> T(Y)
(f =<<) :: m a -> m b
Kleisli范畴中的成分。T以扩展的形式给出
f .T g = f* . g : X -> T(Z)
f <=< g = (f =<<) . g :: a -> m c
并且满足范畴公理
eta .T g = g : Y -> T(Z) Left identity
return <=< g = g :: b -> m c
f .T eta = f : Z -> T(U) Right identity
f <=< return = f :: c -> m d
(f .T g) .T h = f .T (g .T h) : X -> T(U) Associativity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d
应用等价变换
eta .T g = g
eta* . g = g By definition of .T
eta* . g = id . g forall f. id . f = f
eta* = id forall f g h. f . h = g . h ==> f = g
(f .T g) .T h = f .T (g .T h)
(f* . g)* . h = f* . (g* . h) By definition of .T
(f* . g)* . h = f* . g* . h . is associative
(f* . g)* = f* . g* forall f g h. f . h = g . h ==> f = g
在扩展方面是规范给出的
eta* = id : T(X) -> T(X) Left identity
(return =<<) = id :: m t -> m t
f* . eta = f : Z -> T(U) Right identity
(f =<<) . return = f :: c -> m d
(f* . g)* = f* . g* : T(X) -> T(Z) Associativity
(((f =<<) . g) =<<) = (f =<<) . (g =<<) :: m a -> m c
Monads也可以不使用Kleislian扩展来定义,而是在称为join的编程中使用自然转换mu来定义。一个单元是用μ来定义的,它是一个内函子的范畴C上的三元组
T : C -> C
f :: * -> *
和两种自然变形
eta : Id -> T
return :: t -> f t
mu : T . T -> T
join :: f (f t) -> f t
满足等效条件
mu . T(mu) = mu . mu : T . T . T -> T . T Associativity
join . map join = join . join :: f (f (f t)) -> f t
mu . T(eta) = mu . eta = id : T -> T Identity
join . map return = join . return = id :: f t -> f t
然后定义monad类型类
class Functor m => Monad m where
return :: t -> m t
join :: m (m t) -> m t
选项monad的规范mu实现:
instance Monad Maybe where
return = Just
join (Just m) = m
join Nothing = Nothing
concat函数
concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat [] = []
是列表monad的连接。
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> ([a] -> [b])
(f =<<) = concat . map f
联接的实现可以使用等价项从扩展形式转换
mu = id* : T . T -> T
join = (id =<<) :: m (m t) -> m t
从mu到扩展形式的反向转换如下
f* = mu . T(f) : T(X) -> T(Y)
(f =<<) = join . map f :: m a -> m b
Philip Wadler:函数编程的MonadsSimon L Peyton Jones,Philip Wadler:强制函数式编程Jonathan M.D.Hill,Keith Clarke:范畴理论、范畴理论单子及其与函数编程的关系简介´Kleisli类别Eugenio Moggi:计算和单子的概念莫纳德不是什么
但为什么如此抽象的理论对编程有用呢?答案很简单:作为计算机科学家,我们重视抽象!当我们设计软件组件的接口时,我们希望它尽可能少地揭示实现。我们希望能够用许多替代方案来替代实现,许多其他“实例”都是相同的“概念”。当我们为许多程序库设计通用接口时,更重要的是我们选择的接口具有多种实现。我们非常重视monad概念的普遍性,这是因为范畴理论非常抽象,所以它的概念对编程非常有用。因此,我们在下面介绍的单子的推广也与范畴理论有着密切的联系,这一点不足为奇。但我们强调,我们的目的非常实用:它不是“实现范畴理论”,而是找到一种更通用的方法来构造组合子库。数学家已经为我们做了很多工作,这是我们的幸运!
从约翰·休斯的《概括单子到箭头》
解释“什么是monad”有点像说“什么是数字?”我们总是使用数字。但想象一下,你遇到了一个对数字一无所知的人。你怎么解释数字是什么?你怎么开始描述为什么这可能有用?
什么是单子?简单的回答是:这是一种将操作链接在一起的特定方式。
本质上,您正在编写执行步骤,并将它们与“绑定函数”链接在一起。(在Haskell中,它名为>>=。)您可以自己编写对绑定运算符的调用,也可以使用语法糖,使编译器为您插入这些函数调用。但无论哪种方式,每个步骤都由对该绑定函数的调用分隔。
因此绑定函数就像分号;它将流程中的步骤分开。bind函数的任务是获取上一步的输出,并将其输入下一步。
听起来不太难,对吧?但单子不止一种。为什么?怎样
好吧,bind函数可以从一个步骤中获取结果,并将其传递给下一个步骤。但如果这就是单子的全部。。。这实际上不是很有用。理解这一点很重要:每个有用的monad除了做monad之外,还做其他事情。每一个有用的单子都有一种“特殊的力量”,这使它独一无二。
(没有什么特别作用的monad被称为“身份monad”。与身份函数类似,这听起来是一件毫无意义的事情,但事实证明并非如此……但这是另一回事™.)
基本上,每个monad都有自己的绑定函数实现。你可以编写一个绑定函数,这样它就可以在执行步骤之间做一些傻事。例如:
如果每个步骤都返回一个成功/失败指示符,则只有在前一个步骤成功的情况下,才能让绑定执行下一个步骤。这样,失败的步骤“自动”中止整个序列,而无需您进行任何条件测试。(故障单)扩展这个想法,您可以实现“异常”。(错误单点或异常单点。)因为您自己定义它们,而不是将其作为一种语言特性,所以您可以定义它们的工作方式。(例如,您可能希望忽略前两个异常,仅在引发第三个异常时中止。)您可以使每个步骤返回多个结果,并让bind函数对其进行循环,将每个结果输入到下一步。这样,在处理多个结果时,就不必一直到处写循环。绑定函数“自动”为您完成所有这些。(单子)除了将“结果”从一个步骤传递到另一个步骤之外,还可以让bind函数传递额外的数据。这些数据现在不会显示在源代码中,但您仍然可以从任何地方访问它,而无需手动将其传递给每个函数。(《读者》杂志)您可以这样做,以便可以替换“额外数据”。这允许您模拟破坏性更新,而无需实际执行破坏性更新。(莫纳德州及其堂弟作家莫纳德。)因为您只是在模拟破坏性更新,所以您可以轻松地完成真正的破坏性更新所无法完成的事情。例如,您可以撤消上一次更新,或恢复到旧版本。你可以制作一个可以暂停计算的monad,这样你就可以暂停你的程序,进入并修补内部状态数据,然后恢复它。您可以将“continuations”实现为monad。这可以让你打破人们的想法!
所有这些和更多的都可以通过monad实现。当然,这一切在没有单子的情况下也是完全可能的。使用monad非常简单。
事实上,与一般人对蒙得斯的理解相反,他们与国家无关。Monads只是一种包装东西的方法,它提供了对包装好的东西进行操作而不展开的方法。
例如,您可以在Haskell中创建一个类型来包装另一个类型:
data Wrapped a = Wrap a
包装我们定义的东西
return :: a -> Wrapped a
return x = Wrap x
要在不展开的情况下执行操作,假设您有一个函数f::a->b,然后您可以执行此操作来提升该函数以作用于包装的值:
fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)
这就是所有需要理解的。然而,事实证明,有一个更通用的函数来执行此提升,即bind:
bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x
bind可以比fmap做得更多,但反之亦然。实际上,fmap只能用绑定和返回来定义。因此,在定义monad时。。您给出它的类型(这里是Wrapped a),然后说明它的返回和绑定操作是如何工作的。
很酷的是,这是一个普遍的模式,它会在所有地方弹出,以纯方式封装状态只是其中之一。
有关如何使用monad来引入函数依赖关系,从而控制求值顺序(如Haskell的IO monad中所用)的好文章,请查看IOInside。
至于理解单子,不要太担心。读一些你觉得有趣的东西,如果你不马上理解,也不要担心。那就用Haskell这样的语言潜水吧。修道院就是这样一种东西,在那里,通过练习,理解慢慢地进入你的大脑,有一天你突然意识到你理解了它们。
公主对F#计算表达式的解释帮助了我,尽管我仍然不能说我真的理解了。
编辑:这个系列-用javascript解释monad-对我来说是一个“打破平衡”的系列。
http://blog.jcoglan.com/2011/03/05/translation-from-haskell-to-javascript-of-selected-portions-of-the-best-introduction-to-monads-ive-ever-read/http://blog.jcoglan.com/2011/03/06/monad-syntax-for-javascript/http://blog.jcoglan.com/2011/03/11/promises-are-the-monad-of-asynchronous-programming/
我认为理解单子是一件让你毛骨悚然的事。从这个意义上说,尽可能多地阅读“教程”是一个好主意,但通常奇怪的东西(不熟悉的语言或语法)会让你的大脑无法专注于基本内容。
有些事情我很难理解:
基于规则的解释对我来说从未奏效,因为大多数实际示例实际上需要的不仅仅是返回/绑定。此外,称之为规则也无济于事。这更像是“有些东西有共同点,我们把它们称为‘单子’,把共同点称为‘规则’”。Return(a->M<a>)和Bind(M<a>->(a->M<b>)->M<b>)很好,但我永远无法理解Bind如何从M<a>中提取a,以便将其传递给a->M<b>。我不认为我在任何地方读过(也许这对其他人来说都很明显),Return(M<a>->a)的反面必须存在于monad内部,它只是不需要暴露。
解释monad似乎就像解释控制流语句一样。想象一下,一个非程序员要求你解释它们?
你可以给他们一个涉及理论的解释——布尔逻辑、寄存器值、指针、堆栈和框架。但那太疯狂了。
你可以用语法来解释它们。基本上,C中的所有控制流语句都有大括号,您可以通过它们相对于括号的位置来区分条件和条件代码。这可能更疯狂。
或者,您也可以解释循环、if语句、例程、子例程以及可能的协例程。
Monad可以取代相当多的编程技术。语言中有一种特定的语法支持它们,还有一些关于它们的理论。
它们也是函数式程序员使用命令式代码而不承认它的一种方式,但这并不是他们唯一的用途。