如何正确使用尖函数

How to use pointed functor properly

我正在尝试熟悉 JavaScript 中的函数式编程。我刚刚 read 那个指针仿函数是:

An object with an of function that puts any single value into it.

ES2015 adds Array.of making arrays a pointed functor.

我的问题是 "single value" 到底是什么意思?

我想制作一个 Functor/Container(如 https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch8.html),将给定维度(宽度、高度)的网格保存为一维数组,并允许我对其进行转换。作为一个普通对象,我会把它存储为 { width: 2, height: 2, list: [1, 2, 3, 4] } 但我想把它放在一个仿函数中,但我不确定如何正确地做。

我知道像这样使用尖函数来存储单个值是非常好的:

Container.of(47)

但是假设对象是 "single value":

可以使用对象作为值吗
Grid.of({ width: 2, height: 2, list: [1, 2, 3, 4] })

甚至像这样:

Grid.of(2, 2, [1, 2, 3, 4])

But is it ok to use object as value assuming object is a "single value":

是的。 of 应该取 any 值并将其放入容器中。一个对象肯定就是这样一个单一的值。

Grid.of(2, 2, [1, 2, 3, 4])

没有。 of 应该采用单个参数。如果你想把多个值放在一个函子里,先把它们放在另一个结构里,然后把那个结构放在函子里,或者用它的点函数以外的东西构造函子 (of).

Grid.of({ width: 2, height: 2, list: [1, 2, 3, 4] })

不,如果您希望 return 输入那么它将不起作用。 of 应该按原样获取输入并将结构包裹在它周围。对于您的网格,它肯定看起来像这样:

// Grid<A>
class Grid {
    // Int -> Int -> [A] -> Grid<A>
    constructor(w, h, vals) {
        assert(Number.isInteger(w) && Number.isInteger(h));
        this.width = w;
        this.height = h;
        const list = Array.from(vals);
        assert(list.length == w * h);
        this.list = list;
    }
    // Grid<A> -> (A -> B) -> Grid<B>
    map(f) {
        return new Grid(this.width, this.height, this.list.map(f));
    }
    // A -> Grid<A>
    static of(x) {
        return new Grid(1, 1, [x]);
    }
}

所以上面的调用会创建一个 Grid 个对象,而不是四个数字的网格。请注意 of 不是构造仿函数实例的唯一方法,它只是从单个元素构造实例的方法。

请注意,of 作为 Applicative 的一部分最为重要,对于普通的 Functors 来说并不是那么有趣。顺便说一句,如果您对函数式编程概念感兴趣,您还应该能够使您的 Grid 成为 Monoid、Traversable 和 Monad - 请参阅 https://github.com/fantasyland/fantasy-land.

不幸的是https://github.com/hemanth/functional-programming-jargon中的解释不是很准确。

一个 pointed functor 实际上是一个 functor F 以及为 每种类型 定义的函数 of a,并将 a 类型的值 x 发送到 F a 类型的值 of(x)。在 Hindley-Milner signature 中它看起来像这样:

of :: a -> F a

例如,数组仿函数指向 of = x => [x],为任何类型 a 的每个值 x 定义。

此外,函数 of(或者更准确地说,函数的集合 of 因为每种类型都有一个 a)必须是恒等函子的自然转换进入 F。这意味着 of 应用于函数的值等于 of 应用于函数的参数然后映射到函数:

of(f(x)) === of(x).map(f)

例如,在数组示例中,您有

[f(x)] === [x].map(f),

所以x => [x]确实是一个自然的转变

不过你也可以重新定义of

of = x => [x, x]
[f(x), f(x)] === [x, x].map(f)

这使得 Array 成为另一个指向的仿函数,即使 map 方法保持不变。 (请注意,在每种情况下,您只会得到非常特殊的数组作为 of(x) 的值。)

但是,您不能将 of 定义为

of = x => [x, 0]
[f(x), 0] !== [x, 0].map(f)

现在

var grid = Grid.of({ width: 2, height: 2, list: [1, 2, 3, 4] })

完全没问题,returns 您的对象通过包装进入 Grid。然后你可以将 grid 与任何常规函数 f 从普通对象映射到普通对象,结果将与应用 f 并包装到 Grid 相同,因为自然转化规律。请注意,通过这种方式,您还可以使用任何其他值调用 Grid.of,例如 Grid.of({width: 2}) 甚至 Grid.of(2)。或者,您可以限制定义了 Grid.of 的类型,然后该值只能是您允许的类型。


这个有点棘手:

Grid.of(2, 2, [1, 2, 3, 4])

这适用于 Grid.of 多个参数。由于 Grid.of 根据定义是只有一个参数的函数,因此结果将是 Grid.of(2),这可能不是您想要的。如果你真的想提供所有的值,你可能想写

Grid.of([2, 2, [1, 2, 3, 4]])

或者,您可以将 Grid.of 扩展到多个参数,方法是 pre-wrapping 它们在内部数组中,然后应用 Grid.of。这真的取决于你追求什么。

有关真实世界的用法示例,请参见例如here where a "boring" Task is defined via Task.of from a plain value. On the other hand here 是一个更有趣的任务,它包装了一个 Task.of 无法获得的函数。但重要的是,两个任务都可以与两个示例中所示的相同统一界面一起使用。

另请注意,这些示例中没有使用应用函子,因此仍然使用了指向函子而不是应用函子。


已添加。

另请参阅 https://github.com/MostlyAdequate/mostly-adequate-guide-it/blob/master/ch9.md#pointy-functor-factory,了解 Pointed Functor 的精彩介绍和实际应用。