Как правильно использовать остроконечный функтор

Я пытаюсь познакомиться с функциональным программированием на JavaScript. Я только что прочитал этот функтор указателя:

Объект с of функция, которая помещает любое единственное значение в это.

ES2015 добавляет Array.of делая массивы заостренным функтором.

И мой вопрос, что именно означает "одно значение"?

Я хочу сделать Functor/Container (как в https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch8.html), который содержит сетку заданного размера (ширину, высоту) как одномерный массив и позволяет мне делать преобразования на нем. Как простой объект, я бы сохранил его как { width: 2, height: 2, list: [1, 2, 3, 4] } но я хочу поместить это в функтор, и я не уверен, как это сделать правильно.

Я знаю, что это прекрасно, если использовать такой остроконечный функтор для хранения одного значения:

Container.of(47)

Но можно ли использовать объект в качестве значения, предполагая, что объект является "единственным значением":

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

Или даже так:

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

2 ответа

Решение

Объяснение в https://github.com/hemanth/functional-programming-jargon, к сожалению, не очень точное.

Заостренный функтор действительно функтор F вместе с функцией of определяется для каждого типа aи отправка значения x типа a в значение of(x) типа F a, В подписи Хиндли-Милнера это выглядит так:

of :: a -> F a

Например, функтор Array указывается of = x => [x]определяется для каждого значения x любого типа a,

Далее функция of (или, точнее, набор функций of как у вас есть один для каждого типа a) должно быть естественным преобразованием из функтора идентичности в F, Который означает, что of применяется к значению функции равно of применяется к аргументу функции, а затем отображается на функцию:

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

Например, в примере Array у вас есть

[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] })

совершенно нормально и возвращает переданный объект, завернутый в 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 для нескольких аргументов, предварительно заключив их в массив внутри, а затем применяя Grid.of, Это действительно зависит от того, что вы после.

Для примера использования в реальном мире, см., Например, здесь, где "скучная" задача определяется через Task.of из простого значения. С другой стороны, здесь есть более интересная задача, заключающая в себе функцию, с которой вы не получите Task.of, Что важно, так это то, что обе задачи можно использовать с одинаковым унифицированным интерфейсом, как показано в обоих примерах.

Также обратите внимание, что аппликативные функторы не используются в этих примерах, поэтому все еще используются остроконечные функторы, не будучи аппликативными.


ADDED.

См. Также https://github.com/MostlyAdequate/mostly-adequate-guide-it/blob/master/ch9.md#pointy-functor-factory для хорошего ознакомления и реального использования Pointed Functor в реальном мире.

Но можно ли использовать объект в качестве значения, предполагая, что объект является "единственным значением":

Да. of должен принять любое значение и положить его в контейнер. Объект, безусловно, является таким единственным значением.

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

Нет. of должен принимать один параметр. Если вы хотите поместить несколько значений внутри функтора, поместите их внутри другой структуры и поместите эту структуру внутрь функтора или создайте функтор с помощью чего-то другого, чем его функция точки (of).

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

Нет, если вы ожидаете, что он вернет ввод, он не будет работать. 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 наиболее важен как часть аппликатива, не так интересен для обычных функторов. Кстати, если вы заинтересованы в концепциях функционального программирования, вы также должны быть в состоянии сделать Grid Monoid, Traversable и Monad - см. https://github.com/fantasyland/fantasy-land.

Другие вопросы по тегам