Функциональный JavaScript

А что такое вообще "Функциональный ЯП"?

Пытаясь найти определение,
я вспомнил Станислава Лема

Нашёл следующие краткие сведения:

«СЕПУЛЬКИ — важный элемент цивилизации ардритов с планеты Энтеропия. См. СЕПУЛЬКАРИИ».

Я последовал этому совету и прочёл:

«СЕПУЛЬКАРИИ — устройства для сепуления»

Я поискал «Сепуление»; там значилось:

«СЕПУЛЕНИЕ — занятие ардритов с планеты Энтеропия. См. СЕПУЛЬКИ»

Каким все-таки должен быть функциональный язык?

Иммутабельным?

Ленивым?

Декларативным?

А вот и нет!

На самом деле все куда проще

Функциональный язык программирования - язык, основной абстракцией которого является функция.

*при этом функции должны быть обязательно высшего порядка

JavaScript полностью попадает под это определение, ведь даже "класс" в нем - это функция

function User(name, surname){/*....*/}
        
Давайте попробуем применить функциональную концепцию программирования, используя ES6
Давайте начнем со следующих базовых концепциий:
  • Частичное применение
  • Функциональная композиция
  • Карринг
Реализация частичного примерения на ES6

(f, ...args) => (...restArgs) => f(...args.concat(restArgs))
        
Пример использования частичного применения

let p = (f, ...args) => (...restArgs) => f(...args.concat(restArgs))

let add = (a,b) => a + b
let add10 = p(add, 10) 
add10(20) // => 30
        
Примитивная реализация композиции на ES6

(f, g) => (x) => f(g(x))
        
Пример использования композиции

let compose = (f, g) => (x) => f(g(x))

let shout = v => `${v}!`
let loud = v => v.toUpperCase()
let hellYeah = v => `${v}, hell yeah!`

let loudShout = compose(shout, compose(loud, hellYeah))
loudShout('kurskmeetup') // => KURSKMEETUP, HELL YEAH!!
        
Есть еще много полезных функций, но все их реализовывать нет смысла. Проще воспользоваться одной из FP библиотек для JS:
  • Rambda
  • Lodash-fp
  • или другими подобными библиотеками

А теперь поговорим о монадах, функторах и прочем безобразии

Наш первый функтор!


let Container = function(x) {
    this.__value = x
}
Container.of = x => new Container(x)

Container.prototype.map = function(f) {
    return Container.of(f(this.__value))
}
        
При этом map должна отвечать правилу идентичности (Identity)

map(id) === id;
        

И что нам это дает?


Container.of('Kurskmeetup').map(shout)
                          .map(hellYeah)
                          .map(loud) 
                          // => Container("KURSKMEETUP, HELL YEAH!!")
        

Мы уже вплотную подошли к монаде Maybe

Но перед этим давайте решим небольшую задачку :)

Дано


{
  users: [{
      name: "FP-JS",
      id: 0
  }]
}
        
  • Нужно получить имя первого пользователя в коллекции users
  • Поля users может не быть в объекте
  • Коллекция может быть пустой
  • Поля name так же может и не быть :)
  • Код, естественно, не должен падать при работе

Мы можем получить приблизительно следующее


let getName = (obj) => {
  if(obj.users != null && 
      obj.users[0] != null &&
      obj.users[0].name != null) {
    return obj.users[0].name;
  }
}
        

Монада Maybe


//PART 1
let Maybe = function(x) {
    this.__value = x;
}

Maybe.of = x => new Maybe(x)

Maybe.prototype.isNothing = function() {
    return (this.__value === null || this.__value === undefined);
}

Maybe.prototype.map = function(f) {
    return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
}

        

Монада Maybe


//PART 2
Maybe.prototype.ap = function(other) {
    return this.isNothing() ? Maybe.of(null) : other.map(this.__value);
};

Maybe.prototype.join = function() {
    return this.isNothing() ? Maybe.of(null) : this.__value;
}
        

Для чего она нужна?


Maybe.of({event: 'kursk meet up'})
    .map(_.prop('event'))
    .map(_.replace(/\s/g, ''))
    .map(loudShout)
    // => Maybe("KURSKMEETUP, HELL YEAH!!")
        
Но мы ведь можем напороться на null

Maybe.of({})
    .map(_.prop('event'))
    .map(_.replace(/\s/g, ''))
    .map(loudShout)
    // => Maybe(null)
        
Но ничего страшного, этот код не упадет, просто вернется Maybe(null)
Решим нашу задачку, используя ФП

let getName = compose(map(_.prop('name')),
                          xs => Maybe.of(xs[0]),
                          _.prop('users'));
        
Проверки на null не нужны, код можно записать в одну строку
А что если мы хотим взять и безопасно сложить два числа?

_.add(Maybe(10), Maybe(20)) // ERROR
        
Тут на помощь нам спешит такая концепция, как "Апликативный функтор"

Maybe.prototype.ap = function(other) {
    return this.isNothing() ? Maybe.of(null) : other.map(this.__value);
};
        
В завершении немного поиграемся с этим всем :)

Maybe.of(_.add).ap(Maybe.of(2)).ap(Maybe.of(3)); // => Maybe(5)
Maybe.of(_.add).ap(Maybe.of(null)).ap(Maybe.of(3)); // => Maybe(null)

Maybe.of(10).map(_.divide(20)).map(_.add('4')).join() // => 42
        
И это только малая часть того, что можно сделать
  • Можно реализовать такие монады как IO, Either и другие
  • Можно, используя карринг, легко реализовать паттерн Фабрика
  • Можно сделать много всего, но это, к сожалению, не уложится в доклад

Надеюсь, что заинтересовал вас :) Если да, то...

Вам к нему

Brian Lonsdorf

Мои контакты: