可以将std :: unique_ptr视为monad吗?

时间:2019-07-22 17:23:14

标签: c++ functional-programming monads

我目前正在尝试把头缠在单子上。不幸的是,有关该主题的大多数文章都使用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可能没有意义,因为它带有所有者语义。我想知道是否是一个。

3 个答案:

答案 0 :(得分:1)

  

将这些标准应用于C ++,在我看来std::unique_ptr可以视为单子。这是真的吗?

您的定义缺少单子法则,但是我们可以看到std::unique_ptr(加上bindreturn / 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);
}