我目前正在尝试把头缠在单子上。不幸的是,有关该主题的大多数文章都使用Haskell,而没有正确解释该符号。但是,由于我主要使用C ++进行编程,所以我想在不学习新的编程语言的情况下理解monad ...
根据我在网络上收集到的信息,monad M
是类型T
的类型构造函数,它至少提供以下操作:
T
的实际方法T
的转换器(在Haskell中显然称为 return )T
中存储的值应用于函数f
(在Haskell中显然称为 bind )将这些标准应用于C ++,在我看来std::unique_ptr
可以视为单子。这是真的吗?
我的推理如下:
std::unique_ptr
模板用于构造实际类型std::unique_ptr<T>
,因此:
std::unique_ptr<T>{}
或std::make_unique<T>()
std::make_unique
(带有参数...)std::bind(func, pointer.get())
,std::bind(func, *pointer)
或等效的lambda 您是否同意,或者调用operator*()
/ .get()
的组合者会取消std::unique_ptr
成为单子的资格?
我知道,使用std::unique_ptr
作为monad可能没有意义,因为它带有所有者语义。我想知道是否是一个。
答案 0 :(得分:1)
将这些标准应用于C ++,在我看来
std::unique_ptr
可以视为单子。这是真的吗?
您的定义缺少单子法则,但是我们可以看到std::unique_ptr
(加上bind
和return
/ unit
)遵循的适当表述
给予
template <typename T>
std::unique_ptr<T> unit(T t) { return std::make_unique<T>(t); }
template <typename T, typename U, typename F = std::function<std::unique_ptr<U>(T)>>
std::unique_ptr<U> bind(std::unique_ptr<T> ptr, F f) { return ptr ? f(*ptr) : nullptr; }
和一个表达式等价概念(≡
),即“这两个表达式的值相同”
我们需要
bind(unit(a), f) ≡ f(a)
bind(m, unit) ≡ m
bind(bind(m, f), g) ≡ bind(m, [](auto x){
return bind(f(x), g); })
我知道,使用
std::unique_ptr
作为monad可能没有意义,因为它带有所有者语义。我想知道是否是一个。
monad是一种应用语义的事物,例如unique_ptr
的所有权,vector
的多重性或future
的异步性。 C ++中有很多东西是monads,但是(如@NicolBolas所指出的),在monadic结构上没有太多操作。
答案 1 :(得分:0)
尽管名称相同,但我不认为std::bind
和单子绑定是一样的东西。
您的推理是正确的。 return
对于大多数类型来说都是微不足道的。 bind
有点棘手。假设您有一个函数f
,该函数需要一个A
并返回一个std::unique_ptr<B>
。 bind
将需要获取指向f
的指针和一个std::unique_ptr<A>
作为参数,并返回一个std::unique_ptr<B>
。标准库中可能尚未包含此功能,但是编写起来并不难。基本上,它将std:unique_ptr<A>
“解包”为A
,然后在其上调用f
。
人们迫不及待地想要一个适用于任何monad的抽象bind
,就像在Haskell的typeclass中一样。毫无疑问,这使使用 monad更加容易,但是从概念上来说,识别不是必需的。
我个人认为数组monad是最容易掌握的,因为人们非常熟悉数组,甚至可能没有意识到它是monad。假设您有一个函数f
,该函数接受一个字符串并返回该字符串中的字符数组。调用bind(f, ["two", "strings"])
将返回['t', 'w', 'o', 's', 't', 'r', 'i', 'n', 'g', 's']
。
答案 2 :(得分:-1)
(将这些标准应用于C ++,在我看来
std::unique_ptr
可以认为是monad)是真的吗?
不,那不是C ++或任何功能性编程语言中的monad。
对组合器的调用
operator*()/.get()
是否会使std :: unique_ptr不再成为单子?
在C ++中,monad将是一个参数函子。该函子可以返回结果,也可以返回另一个参数函子。
std::unique_ptr
不会这样做。
注意:接下来是有问题的上下文,可能很有用。
C ++不是一种功能编程语言。在某些特定领域中,可能会在C ++中使用monad,但总的来说,monad不是惯用的C ++。
这是C ++中monad的示例。
#include <iostream>
int main() {
auto cf = [](auto& a) mutable {
return [&a](auto b) mutable {
return [&a,b](auto c) mutable {
return [&a,b,c](auto d) mutable {
a << (b * c + d) << "\n"; return b * c + d;
};
};
};
};
auto cf_cout = cf(std::cout);
auto cf_cout_3 = cf_cout(3);
auto cf_cout_3_10 = cf_cout_3(10);
for (int i = 5; i < 9; ++i) cf_cout_3_10(i);
cf_cout(5)(20)(7);
}
注意cf(对于“ curried function”)如何逐步组成一个参数并返回一个新的函子?单论函子是单子。
在JavaScript中,可以做类似的事情(为简单起见,尽管只是做一个乘法):
const cf = (a) => (b) => (c) => (d) => { return a * b * c * d; }
const answer = cf(3)(4)(5)(6)
const paf = cf(3)(4)
const answer2 = paf(3)(4)
即使在JavaScript中,它对使用monads进行函数式编程稍微友好一些,并提供了一些不错的语法糖,但它仍然不是惯用的JavaScript。
像F#这样的函数式编程语言在语法上使这种编程风格变得容易,因为所有内容都经过严格处理:
let cf a b c d = a * b * c * d
let answer = cf 3 4 5 6
let paf = cf 3 4
let answer2 = paf 3 4
单子键的关键是柯里化。 (不要与部分应用程序混为一谈。)默认情况下,C ++不咖喱。任何虚张声势都必须选择加入。
这是一个快速而肮脏的currying模板:
template< class, class = std::void_t<> > struct
needs_unapply : std::true_type { };
template< class T > struct
needs_unapply<T, std::void_t<decltype(std::declval<T>()())>> : std::false_type { };
template <typename F> auto
curry(F&& f) {
if constexpr (needs_unapply<decltype(f)>::value) {
return [=](auto&& x) {
return curry([=](auto&&...xs) -> decltype(f(x,xs...)) { return f(x,xs...); });
};
}
else {
return f();
}
}
int main() {
auto cf = curry([](auto a, auto b, auto c, auto d) { return a * b * c * d; });
// Calling a curried function is a series of function calls,
// since each function takes one-and-only-one argument.
auto const answer = cf(3)(4)(5)(6);
}