什么是我的应用函子不能与 Ramda 的 ap 一起工作?

What is my applicative functor not working with Ramda's ap?

我正在尝试学习如何在 javascript 中使用应用函子,并遇到了 ap 方法。我正在尝试使用它来组合三个数组,如下所示:

const products = ['teeshirt', 'sweater']
const options = ['large', 'medium', 'small']
const colors = ['red', 'black']

所以根据 documentation 我正在尝试这个:

const buildMerch = product => option => color =>`${product}-${option}-${color}`

const merchandise = R.ap([buildMerch], [products, options, colors])

但这给了我三个功能:

[function (option) {
  return function (color) {
    return product + '-' + option + '-' + color;
  };
}, function (option) {
  return function (color) {
    return product + '-' + option + '-' + color;
  };
}, function (option) {
  return function (color) {
    return product + '-' + option + '-' + color;
  };
}]

...而不是我期望的数组的组合结果:

["teeshirt- large-red", "teeshirt- large-black", "teeshirt- medium-red", "teeshirt- medium-black", "teeshirt- small-red", "teeshirt- small-black", "sweater- large-red", "sweater- large-black", "sweater- medium-red", "sweater- medium-black", "sweater- small-red", "sweater- small-black"]

我做错了什么?我该如何解决这个问题?

这是一个有问题的 jsbin:https://jsbin.com/fohuriy/14/edit?js,console

根据文档,

ap 将函数列表应用于值列表。您的函数 buildMerch 具有以下 "type":

buildMerch :: String -> String -> String -> String

最简单的 apmap:对于任何应用函子,我们得到:

pure f <*> a
  ======
map f a

对于数组,purex => [x]。所以,

R.ap([buildMerch], [foo, bar, baz])
  ======
R.map(buildMerch, [foo, bar, baz])

通过将 buildMerch 映射到参数列表,我们将其部分应用于相关数组。做你想做的表达是:

const merchandise = R.ap(R.ap(R.map(buildMerch, products), options), colors);

首先,我们将 buildMerch 映射到产品数组。这给了我们一个带有两个参数的函数数组:[String -> String -> String]。然后,我们使用 R.ap 将其与 options :: [String] 组合,后者将第一个数组中的每个函数与 options 数组中的每个参数一起应用。现在我们有 [String -> String],最后我们 R.apcolors 得到你想要的最终字符串数组。

AP 将函数列表应用于值列表。在您的情况下,您将对指定数组中的每个元素调用 buildMerch,即 products,然后是 options,然后是 colors,而不是每个组合你的数组。这与您期望三个参数的方法签名不匹配。

解决这个问题的另一种方法是将ap添加到原生javascript数组。通过这种方式,您实际上是将 Array 变成了一个应用仿函数,您不需要库,并且它与您可能想要使用的任何其他应用仿函数使用相同的接口。

// add ap
Array.prototype.ap = function(anotherArray) {
  return this.map(el =>
    anotherArray.map(el)
  ).flatten();
};

这依赖于展平(或'join')。

// add flatten
Array.prototype.flatten = function() {
  let results = [];
  this.forEach(subArray => {
    subArray.forEach(item => {
      results.push(item);
    });
  });
  return results;
};

现在:

const products = ['teeshirt', 'sweater'];
const options = ['large', 'medium', 'small'];
const colors = ['red', 'black'];
const buildMerch = product => option => color =>`${product}-${option}-${color}`;

const merchandise = products.map(buildMerch).ap(options).ap(colors);

现在你也可以举起所有三个:

const liftA3 = (fn, functor1, functor2, functor3) =>
  functor1.map(fn).ap(functor2).ap(functor3);

liftA3(buildMerch, products, options, colors) // also returns merchandise