就OOP程序员所能理解的(没有任何功能编程背景)而言,什么是monad?
它解决了什么问题,最常用的地方是什么?
使现代化
为了澄清我所寻求的理解,假设您正在将一个具有monad的FP应用程序转换为OOP应用程序。如何将monad的职责移植到OOP应用程序?
就OOP程序员所能理解的(没有任何功能编程背景)而言,什么是monad?
它解决了什么问题,最常用的地方是什么?
使现代化
为了澄清我所寻求的理解,假设您正在将一个具有monad的FP应用程序转换为OOP应用程序。如何将monad的职责移植到OOP应用程序?
当前回答
按照OOP程序员将理解(没有任何功能编程背景),什么是莫纳德?它解决了什么问题是最常用的地方吗?是最常用的地方吗?
就OO编程而言,monad是一个接口(或者更可能是一个mixin),由一个类型参数化,具有两个方法,return和bind,它们描述:
如何注入值以获得注入值的一元值类型如何使用从非一元值。
它解决的问题与您期望的任何接口的问题类型相同,“我有很多不同的类,它们做不同的事情,但似乎以一种具有潜在相似性的方式来做这些不同的事情。即使这些类本身不是比‘Object’类本身更接近的子类,我如何描述它们之间的相似性?”
更具体地说,Monad“接口”与IEnumerator或IIterator相似,因为它采用的类型本身也采用的类型。然而,Monad的主要“点”是能够连接基于内部类型的操作,甚至可以连接到具有新的“内部类型”的点,同时保持-甚至增强-主类的信息结构。
其他回答
可选/可能是最基本的一元类型
单子是关于功能组成的。如果函数f:可选<A>->可选<B>,g:可选<B>->可选<C>,h:可选<C>->可选<D>。然后你可以创作它们
optional<A> opt;
h(g(f(opt)));
monad类型的好处是,您可以改为组合f:A->可选<B>、g:B->可选<C>、h:C->可选<D>。他们可以这样做,因为monadic接口提供了绑定运算符
auto optional<A>::bind(A->optional<B>)->optional<B>
并且可以写作文
optional<A> opt
opt.bind(f)
.bind(g)
.bind(h)
monads的好处是我们不再需要处理if(!opt)return nullopt的逻辑;在f、g、h中的每一个中,因为该逻辑被移动到绑定运算符中。
ranges/lists/iterables是第二种最基本的monad类型。
范围的一元特征是我们可以变换然后变平,即从一个整数范围内编码的表示开始[36,98]
我们可以转换为[[m','a','c','h','i','n','e',''],['l','','r','n','i','n','g','.']]
然后压平[am','a','c','h','i','n','e','l',''e'
而不是编写此代码
vector<string> lookup_table;
auto stringify(vector<unsigned> rng) -> vector<char>
{
vector<char> result;
for(unsigned key : rng)
for(char ch : lookup_table[key])
result.push_back(ch);
result.push_back(' ')
result.push_back('.')
return result
}
我们可以写这个
auto f(unsigned key) -> vector<char>
{
vector<char> result;
for(ch : lookup_table[key])
result.push_back(ch);
return result
}
auto stringify(vector<unsigned> rng) -> vector<char>
{
return rng.bind(f);
}
monad将for循环(无符号键:rng)向上推到绑定函数中,从而允许理论上更容易推理的代码。毕达哥拉斯三元组可以在范围-v3中使用嵌套绑定生成(而不是我们看到的可选的链式绑定)
auto triples =
for_each(ints(1), [](int z) {
return for_each(ints(1, z), [=](int x) {
return for_each(ints(x, z), [=](int y) {
return yield_if(x*x + y*y == z*z, std::make_tuple(x, y, z));
});
});
});
来自维基百科:
在函数式编程中,monad是一种抽象数据类型,用于表示计算(而不是域模型中的数据)。Monads公司允许程序员链接动作一起构建管道,其中每个动作都用提供了其他处理规则莫纳德。编写的程序功能性风格可以利用monads来构造程序包括顺序操作,1[2]或定义任意控制流(如处理并发,延续或例外)。形式上,monad由定义两个操作(bind和return)和类型构造函数M必须满足几个财产才能允许正确组成一元函数(即使用monad中的值作为参数)。返回操作需要一个普通类型的值,并将其放入装入M型一元容器中。绑定操作执行反向处理,提取集装箱的原始价值,以及将其传递给关联的下一个函数。程序员将编写monadic定义数据处理的函数管道monad充当框架,因为它是一种可重用的行为这决定了调用管道,并管理所有需要的卧底工作计算[3] 绑定和返回管道中交错的运算符将在每个monadic之后执行函数返回控制,并将注意特定方面由monad处理。
我相信这很好地解释了这一点。
在OO术语中,monad是一个流畅的容器。
最低要求是类<a>的定义,它支持构造函数Something(a a)和至少一个方法Something<B>flatMap(函数<a,Something<B>>)
可以说,monad类是否有签名Something<B>work()的方法来保存类的规则——编译器在编译时在flatMap中烘焙。
为什么单子有用?因为它是一个允许保留语义的可链式操作的容器。例如,可选<?>为Optional<String>、Optional<Integer>、Optional<MyClass>等保留isPresent的语义。
作为一个粗略的例子,
Something<Integer> i = new Something("a")
.flatMap(doOneThing)
.flatMap(doAnother)
.flatMap(toInt)
注意,我们以字符串开头,以整数结尾。很酷。
在OO中,这可能需要一点努力,但Something上的任何方法如果返回Something的另一个子类,都符合返回原始类型容器的容器函数的标准。
这就是保持语义的方式——即容器的含义和操作不会改变,它们只是包装和增强容器内的对象。
monad是一个函数数组
(Pst:函数数组只是一个计算)。
实际上,这些函数不是真正的数组(一个单元格数组中的一个函数),而是由另一个函数>>=链接。>>=允许调整函数i的结果以馈送函数i+1,并在它们之间执行计算或者甚至不调用函数i+1。
这里使用的类型是“带上下文的类型”。这是一个带有“标记”的值。被链接的函数必须采用“裸值”并返回标记结果。>>=的职责之一是从上下文中提取裸值。还有一个函数“return”,它接受一个裸值并将其与一个标记一起放置。
Maybe的一个例子。让我们使用它来存储一个简单的整数,以便进行计算。
-- a * b
multiply :: Int -> Int -> Maybe Int
multiply a b = return (a*b)
-- divideBy 5 100 = 100 / 5
divideBy :: Int -> Int -> Maybe Int
divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING
divideBy denom num = return (quot num denom) -- quotient of num / denom
-- tagged value
val1 = Just 160
-- array of functions feeded with val1
array1 = val1 >>= divideBy 2 >>= multiply 3 >>= divideBy 4 >>= multiply 3
-- array of funcionts created with the do notation
-- equals array1 but for the feeded val1
array2 :: Int -> Maybe Int
array2 n = do
v <- divideBy 2 n
v <- multiply 3 v
v <- divideBy 4 v
v <- multiply 3 v
return v
-- array of functions,
-- the first >>= performs 160 / 0, returning Nothing
-- the second >>= has to perform Nothing >>= multiply 3 ....
-- and simply returns Nothing without calling multiply 3 ....
array3 = val1 >>= divideBy 0 >>= multiply 3 >>= divideBy 4 >>= multiply 3
main = do
print array1
print (array2 160)
print array3
为了说明monad是带有助手操作的函数数组,请考虑与上述示例等效,仅使用一个实函数数组
type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions
myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3]
-- function for the machinery of executing each function i with the result provided by function i-1
runMyMonad :: Maybe Int -> MyMonad -> Maybe Int
runMyMonad val [] = val
runMyMonad Nothing _ = Nothing
runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs
它的用法如下:
print (runMyMonad (Just 160) myArray1)
我能想到的最简单的解释是,单声道是一种用符号化结果组成函数的方式(也称为克莱斯利合成)。“embelished”函数具有签名a->(b,smth),其中a和b是可能彼此不同但不一定不同的类型(想想Int,Bool),smth是“上下文”或“embelisement”。
这种类型的函数也可以写成a->m b,其中m相当于“embelisation”smth。因此,这些是在上下文中返回值的函数(想想记录其操作的函数,其中smth是日志消息;或者执行输入/输出的函数,其结果取决于IO操作的结果)。
monad是一个接口(“typeclass”),它让实现者告诉它如何组合这样的函数。实现者需要为任何想要实现接口的m类型定义一个组合函数(a->mb)->(b->mc)->(a->mc)(这是Kleisli组合)。
所以,如果我们说我们有一个元组类型(Int,String),它表示Int上的计算结果,(_,String)是“embelisation”-动作的日志-和两个函数increment::Int->(Int,String)和twoTimes::Int->(Int、String),我们希望获得一个函数incamentThenDouble::Int->(Int),这是两个函数的组合,也考虑了日志。
在给定的示例中,两个函数的monad实现应用于整数值2增量ThenDouble 2(等于2倍(增量2))将返回(6,“加法1”。中间结果的增量2等于(3,“加1”),2乘以3等于(6,“加3”)
从这个Kleisli合成函数可以导出通常的一元函数。