У функционального программирования много преимуществ, и его популярность постоянно растет. Но, как и у любой парадигмы программирования, у ФП есть свой жаргон. Мы решили сделать небольшой словарь для всех, кто знакомится с ФП.


В примерах используется JavaScript ES2015). (Почему JavaScript?)


Работа над материалом продолжается; присылайте свои пулл-реквесты в оригинальный репозиторий на английском языке.


В документе используются термины из спецификации Fantasy Land spec по мере необходимости.


Arity (арность)


Количество аргументов функции. От слов унарный, бинарный, тернарный (unary, binary, ternary) и так далее. Это необычное слово, потому что состоит из двух суффиксов: "-ary" и "-ity.". Сложение, к примеру, принимает два аргумента, поэтому это бинарная функция, или функция, у которой арность равна двум. Иногда используют термин "диадный" (dyadic), если предпочитают греческие корни вместо латинских. Функция, которая принимает произвольное количество аргументов называется, соответственно, вариативной (variadic). Но бинарная функция может принимать два и только два аргумента, без учета каррирования или частичного применения.


const sum = (a, b) => a + b

const arity = sum.length
console.log(arity) // 2

// The arity of sum is 2

Higher-Order Functions (функции высокого порядка)


Функция, которая принимает функцию в качестве аргумента и/или возвращает функцию.


const filter = (predicate, xs) => {
  const result = []
  for (let idx = 0; idx < xs.length; idx++) {
    if (predicate(xs[idx])) {
      result.push(xs[idx])
    }
  }
  return result
}

const is = (type) => (x) => Object(x) instanceof type

filter(is(Number), [0, '1', 2, null]) // [0, 2]

Partial Application (частичное применение)


Частичное применение функции означает создание новой функции с пред-заполнением некоторых аргументов оригинальной функции.


// Helper to create partially applied functions
// Takes a function and some arguments
const partial = (f, ...args) =>
  // returns a function that takes the rest of the arguments
  (...moreArgs) =>
    // and calls the original function with all of them
    f(...args, ...moreArgs)

// Something to apply
const add3 = (a, b, c) => a + b + c

// Partially applying `2` and `3` to `add3` gives you a one-argument function
const fivePlus = partial(add3, 2, 3) // (c) => 2 + 3 + c

fivePlus(4) // 9

Также в JS можно использовать Function.prototype.bind для частичного применения функции:


const add1More = add3.bind(null, 2, 3) // (c) => 2 + 3 + c

Благодаря предварительной подготовке данных частичное применение помогает создавать более простые функции из более сложных. Функции с каррированием автоматически выполняют частичное применение.


Currying (каррирование)


Процесс конвертации функции, которая принимает несколько аргументов, в функцию, которая принимает один аргумент за раз.


При каждом вызове функции она принимает один аргумент и возвращает функцию, которая принимает один аргумент до тех пор, пока все аргументы не будут обработаны.


const sum = (a, b) => a + b

const curriedSum = (a) => (b) => a + b

curriedSum(40)(2) // 42.

const add2 = curriedSum(2) // (b) => 2 + b

add2(10) // 12

Auto Currying (автоматическое каррирование)


Трансформация функции, которая принимает несколько аргументов, в новую функцию. Если в новую функцию передать меньшее чем предусмотрено количество аргументов, то она вернет функцию, которая принимает оставшиеся аргументы. Когда функция получает правильное количество аргументов, то она исполняется.


В Underscore, lodash и ramda есть функция curry.


const add = (x, y) => x + y

const curriedAdd = _.curry(add)
curriedAdd(1, 2) // 3
curriedAdd(1) // (y) => 1 + y
curriedAdd(1)(2) // 3

Дополнительные материалы



Function Composition (композиция функций)


Соединение двух функций для формирования новой функции, в которой вывод первой функции является вводом второй.


const compose = (f, g) => (a) => f(g(a)) // Definition
const floorAndToString = compose((val) => val.toString(), Math.floor) // Usage
floorAndToString(121.212121) // '121'

Purity (чистота)


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


const greet = (name) => 'Hi, ' + name

greet('Brianne') // 'Hi, Brianne'

В отличие от:



let greeting

const greet = () => {
  greeting = 'Hi, ' + window.name
}

greet() // "Hi, Brianne"

Side effects (побочные эффекты)


У функции есть побочные эффекты если кроме возврата значения она взаимодействует (читает или пишет) с внешним изменяемым состоянием.


const differentEveryTime = new Date()

console.log('IO is a side effect!')

Idempotent (идемпотентность)


Функция является идемпотентной если повторное ее исполнение производит такой же результат.


f(f(x)) ? f(x)

Math.abs(Math.abs(10))

sort(sort(sort([2, 1])))

Point-Free Style (бесточечная нотация)


Написание функций в таком виде, что определение не явно указывает на количество используемых аргументов. Такой стиль обычно требует каррирования или другой функции высокого порядка (или в целом — неявного программирования).


// Given
const map = (fn) => (list) => list.map(fn)
const add = (a) => (b) => a + b

// Then

// Not points-free - `numbers` is an explicit argument
const incrementAll = (numbers) => map(add(1))(numbers)

// Points-free - The list is an implicit argument
const incrementAll2 = map(add(1))

Функция incrementAll определяет и использует параметр numbers, так что она не использует бесточечную нотацию. incrementAll2 просто комбинирует функции и значения, не упоминая аргументов. Она использует бесточечную нотацию.


Определения с бесточечной нотацией выглядят как обычные присваивания без function или =>.


Predicate (предикат)


Предикат — это функция, которая возвращает true или false в зависимости от переданного значения. Распространенный случай использования предиката — функция обратного вызова (callback) для фильтра массива.


const predicate = (a) => a > 2

;[1, 2, 3, 4].filter(predicate) // [3, 4]

Categories (категории)


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


Value (значение)


Все, что может быть присвоено переменной.


5
Object.freeze({name: 'John', age: 30}) // The `freeze` function enforces immutability.
;(a) => a
;[1]
undefined

Constant (константа)


Переменная, которую нельзя переназначить после определения.


const five = 5
const john = {name: 'John', age: 30}

Константы обладают референциальной прозрачностью или прозрачностью ссылок (referential transparency). То есть, их можно заменить значениями, которые они представляют, и это не повлияет на результат.


С константами из предыдущего листинга следующее выражение выше всегда будет возвращать true.


john.age + five === ({name: 'John', age: 30}).age + (5)

Functor (функтор)


Объект, который реализует функцию map, которая при проходе по всем значениям в объекте создает новый объект, и подчиняется двум правилам:


// сохраняет нейтральный элемент (identity)
object.map(x => x) === object

и


// поддерживает композицию
object.map(x => f(g(x))) === object.map(g).map(f)

(f, g — произвольные функции)


В JavaScript есть функтор Array, потому что он подчиняется эти правилам:


[1, 2, 3].map(x => x) // = [1, 2, 3]

и


const f = x => x + 1
const g = x => x * 2

;[1, 2, 3].map(x => f(g(x))) // = [3, 5, 7]
;[1, 2, 3].map(g).map(f)     // = [3, 5, 7]

Pointed Functor (указывающий функтор)


Объект с функцией of с любым значением. В ES2015 есть Array.of, что делает массивы указывающим функтором.


Array.of(1) // [1]

Lift


Lifting — это когда значение помещается в объект вроде функтора. Если "поднять" (lift) функцию в аппликативный функтор, то можно заставить ее работать со значениями, которые также присутствуют в функторе.


В некоторых реализациях есть функция lift или liftA2, которые используются для упрощения запуска функций на функторах.


const liftA2 = (f) => (a, b) => a.map(f).ap(b)

const mult = a => b => a * b

const liftedMult = liftA2(mult) // this function now works on functors like array

liftedMult([1, 2], [3]) // [3, 6]
liftA2((a, b) => a + b)([1, 2], [3, 4]) // [4, 5, 5, 6]

Подъем функции с одним аргументом и её применение выполняет то же самое, что и map.


const increment = (x) => x + 1

lift(increment)([2]) // [3]
;[2].map(increment) // [3]

Referential Transparency (прозрачность ссылок)


Если выражение можно заменить его значением без влияния на поведение программы, то оно обладает прозрачностью ссылок.


Например, есть функция greet:


const greet = () => 'Hello World!'

Любой вызов greet() можно заменить на Hello World!, так что эта функция является прозрачной (referentially transparent).


Lambda (лямбда)


Анонимная функция, которую можно использовать как значение.


;(function (a) {
  return a + 1
})

;(a) => a + 1

Лямбды часто передают в качестве аргументов в функции высокого порядка.


[1, 2].map((a) => a + 1) // [2, 3]

Лямбду можно присвоить переменной.


const add1 = (a) => a + 1

Lambda Calculus (лямбда-исчисление)


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


Lazy evaluation (ленивые вычисления)


Механизм вычисления при необходимости, с задержкой вычисления выражения до того момента, пока значение не потребуется. В функциональных языках это позволяет создавать структуры вроде бесконечных списков, которые в обычных условиях невозможны в императивных языках программирования, где очередность команд имеет значение.


const rand = function*() {
  while (1 < 2) {
    yield Math.random()
  }
}

const randIter = rand()
randIter.next() // Каждый вызов дает случайное значение, выражение исполняется при необходимости.

Monoid (моноид)


Объект с функцией, которая "комбинирует" объект с другим объектом того же типа. Простой пример моноида это сложение чисел:


1 + 1 // 2

В этом случае число — это объект, а + это функция.


Должен существовать нейтральный элемент (identity), так, чтобы комбинирование значения с ним не изменяло значение. В случае сложения таким элементом является 0.


1 + 0 // 1

Также необходимо, чтобы группировка операций не влияла на результат (ассоциативность):


1 + (2 + 3) === (1 + 2) + 3 // true

Конкатенация массивов — это тоже моноид:


;[1, 2].concat([3, 4]) // [1, 2, 3, 4]

Нейтральный элемент — это пустой массив []


;[1, 2].concat([]) // [1, 2]

Если существуют функции нейтрального элемента и композиции, то функции в целом формируют моноид:


const identity = (a) => a
const compose = (f, g) => (x) => f(g(x))

foo — это любая функция с одним аргументом.


compose(foo, identity) ? compose(identity, foo) ? foo

Monad (монада)


Монада — это объект с функциями of и chain. chain похож на map, но он производит разложение вложенных объектов в результате.


// Implementation
Array.prototype.chain = function (f) {
  return this.reduce((acc, it) => acc.concat(f(it)), [])
}

// Usage
;Array.of('cat,dog', 'fish,bird').chain((a) => a.split(',')) // ['cat', 'dog', 'fish', 'bird']

// Contrast to map
;Array.of('cat,dog', 'fish,bird').map((a) => a.split(',')) // [['cat', 'dog'], ['fish', 'bird']]

of также известен как return в других функциональных языках.
chain также известен как flatmap и bind в других языках.


Comonad (комонада)


Объект с функциями extract и extend.


const CoIdentity = (v) => ({
  val: v,
  extract () {
    return this.val
  },
  extend (f) {
    return CoIdentity(f(this))
  }
})

Extract берет значение из функтора.


CoIdentity(1).extract() // 1

Extend выполняет функцию на комонаде. Функция должна вернуть тот же тип, что комонада.


CoIdentity(1).extend((co) => co.extract() + 1) // CoIdentity(2)

Applicative Functor (аппликативный функтор)


Объект с функцией ap. ap применяет функцию в объекте к значению в другом объекте того же типа.


// Implementation
Array.prototype.ap = function (xs) {
  return this.reduce((acc, f) => acc.concat(xs.map(f)), [])
}

// Example usage
;[(a) => a + 1].ap([1]) // [2]

Это полезно, когда есть два объекта, и нужно применить бинарную операцию на их содержимом.


// Arrays that you want to combine
const arg1 = [1, 3]
const arg2 = [4, 5]

// combining function - must be curried for this to work
const add = (x) => (y) => x + y

const partiallyAppliedAdds = [add].ap(arg1) // [(y) => 1 + y, (y) => 3 + y]

В итоге получим массив функций, которые можно вызвать с ap чтобы получить результат:


partiallyAppliedAdds.ap(arg2) // [5, 6, 7, 8]

Morphism (морфизм)


Функция трансформации.


Endomorphism (эндоморфизм)


Функция, у которой ввод и вывод — одного типа.


// uppercase :: String -> String
const uppercase = (str) => str.toUpperCase()

// decrement :: Number -> Number
const decrement = (x) => x - 1

Isomorphism (изоморфизм)


Пара структурных трансформаций между двумя типами объектов без потери данных.


Например, двумерные координаты можно хранить в массиве [2,3] или объекте {x: 2, y: 3}.


// Providing functions to convert in both directions makes them isomorphic.
const pairToCoords = (pair) => ({x: pair[0], y: pair[1]})

const coordsToPair = (coords) => [coords.x, coords.y]

coordsToPair(pairToCoords([1, 2])) // [1, 2]

pairToCoords(coordsToPair({x: 1, y: 2})) // {x: 1, y: 2}

Setoid


Объект, у которого есть функция equals, которую можно использовать для сравнения объектов одного типа.


Сделать массив сетоидом:


Array.prototype.equals = (arr) => {
  const len = this.length
  if (len !== arr.length) {
    return false
  }
  for (let i = 0; i < len; i++) {
    if (this[i] !== arr[i]) {
      return false
    }
  }
  return true
}

;[1, 2].equals([1, 2]) // true
;[1, 2].equals([0]) // false

Semigroup (полугруппа)


Объект с функцией concat, которая комбинирует его с другим объектом того же типа.


;[1].concat([2]) // [1, 2]

Foldable


Объект, в котором есть функция reduce, которая трансформирует объект в другой тип.


const sum = (list) => list.reduce((acc, val) => acc + val, 0)
sum([1, 2, 3]) // 6

Type Signatures (сигнатуры типа)


Часто функции в JavaScript содержат комментарии с указанием типов их аргументов и возвращаемых значений. В сообществе существуют разные подходы, но они все схожи:


// functionName :: firstArgType -> secondArgType -> returnType

// add :: Number -> Number -> Number
const add = (x) => (y) => x + y

// increment :: Number -> Number
const increment = (x) => x + 1

Если функция принимает другую функцию в качестве аргумента, то ее помещают в скобки.


// call :: (a -> b) -> a -> b
const call = (f) => (x) => f(x)

Символы a, b, c, d показывают, что аргументы могут быть любого типа. Следующая версия функции map принимает:


  1. функцию, которая трансформирует значение типа a в другой тип b
  2. массив значений типа a,

и возвращает массив значений типа b.


// map :: (a -> b) -> [a] -> [b]
const map = (f) => (list) => list.map(f)

Дополнительные материалы



Union type (тип-объединение)


Комбинация двух типов в один, новый тип.


В JavaScript нет статических типов, но давайте представим, что мы изобрели тип NumOrString, который является сложением String и Number.


Операция + в JavaScript работает со строками и числами, так что можно использовать наш новый тип для описания его ввода и вывода:


// add :: (NumOrString, NumOrString) -> NumOrString
const add = (a, b) => a + b

add(1, 2) // Возвращает число 3
add('Foo', 2) // Возвращает строку "Foo2"
add('Foo', 'Bar') // Возвращает строку "FooBar"

Тип-объединение также известно как алгебраический тип, размеченное объединение и тип-сумма.


Существует пара библиотек в JavaScript для определения и использования таких типов.


Product type (тип-произведение)


Тип-произведение комбинирует типы таким способом, который вам скорее всего знаком:


// point :: (Number, Number) -> {x: Number, y: Number}
const point = (x, y) => ({x: x, y: y})

Его называют произведением, потому что возможное значение структуры данных это произведение (product) разных значений.


См. также: теория множеств.


Option (опцион)


Тип-объединение с двумя случаями: Some и None. Полезно для композиции функций, которые могут не возвращать значения.


// Naive definition

const Some = (v) => ({
  val: v,
  map (f) {
    return Some(f(this.val))
  },
  chain (f) {
    return f(this.val)
  }
})

const None = () => ({
  map (f) {
    return this
  },
  chain (f) {
    return this
  }
})

// maybeProp :: (String, {a}) -> Option a
const maybeProp = (key, obj) => typeof obj[key] === 'undefined' ? None() : Some(obj[key])

Используйте chain для построения последовательности функций, которые возвращают Option.



// getItem :: Cart -> Option CartItem
const getItem = (cart) => maybeProp('item', cart)

// getPrice :: Item -> Option Number
const getPrice = (item) => maybeProp('price', item)

// getNestedPrice :: cart -> Option a
const getNestedPrice = (cart) => getItem(obj).chain(getPrice)

getNestedPrice({}) // None()
getNestedPrice({item: {foo: 1}}) // None()
getNestedPrice({item: {price: 9.99}}) // Some(9.99)

Option также известен как Maybe. Some иногда называют Just. None иногда называют Nothing.


Библиотеки функционального программирования в JavaScript


Сколько терминов вам были известны до прочтения статьи?

Проголосовало 240 человек. Воздержалось 28 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Поделиться с друзьями
-->

Комментарии (51)


  1. koldyr
    16.09.2016 13:28
    +3

    Очень странное определение категории. Морфизмы в катеориях это не всегда функции. И определенных правил всего три: Существование композиции, существование единицы и ассоциативность композиции.


    1. xGromMx
      16.09.2016 13:29
      +12

      Тут вообще очень много странных определений


  1. xGromMx
    16.09.2016 13:36
    +6

    Если реально хотите понять про ФП, то лучше почитайте книги о Haskell. Из русскоязычного настоятельно рекомендую http://anton-k.github.io/ru-haskell-book/book/toc.html тут и про ФП и про ТК есть отчасти достаточно интуитивно понятно (вообще это наверное лучшее, что я читал из бесплатных онлайн книг на русском языке)


    1. ForNeVeR
      16.09.2016 15:25
      +1

      1. xGromMx
        16.09.2016 15:26
        +1

        Есть в моем списке в комментарии ниже


    1. defaultvoice
      16.09.2016 16:38

      Вдогонку порекомендую и это https://www.ohaskell.guide/


  1. xGromMx
    16.09.2016 15:07
    +2

    Еще по поводу ТК https://www.youtube.com/watch?v=I8LbkfSSR58&list=PLbgaMIhjbmEnaH_LTkxLI7FMa2HsnawM_ и вот подборка по ФП https://github.com/xgrommx/awesome-functional-programming (welcome to PR)


    1. Warlock_29A
      16.09.2016 16:36

      Дополню, что у Bartosz Milewski помимо ТК, так же можно и по Haskell лекции посмотреть.


  1. greendimka
    16.09.2016 15:18
    -10

    По-моему приверженцам "функционального" программирования чего-то в жизни не хватает.
    Вот объясните мне (простому приверженцу принципа "работа должна быть сделана качественно и в сроки") — какую ценность несёт, хотя бы и подразделение функций на унарные, бинарные, тернарные и т. д.?
    Сколько не читаю про так называемое ФП (функциональное программирование), так и не могу понять, что же в нём особенного? Функции используются практически в любом языке, практически в любой архитектуре. Нафига вводить еще один жаргон? Время девать некуда?


    1. 80x86
      16.09.2016 15:41
      +3

      Узость мышления зачастую компенсируется яростностью отстаивания точки зрения.
      ФП помогает расширить интеллектуальный горизонт, например :) Как по мне, так хотя бы для этого оно нужно. Наверное.


      1. greendimka
        16.09.2016 15:59
        -1

        То есть разложение на категории ради разложения и присвоение терминов ради присвоения — это и есть расширение мышления? По-моему это больше походит на подмену понятий, когда знание большого количества фактов пытаются выдать за умение мыслить.
        Поэтому повторю свой основной вопрос: нафига? Время девать некуда?

        И перенесу вопрос в другую плоскость: вот переписываемся мы с вами на великом и могучем. Я, честно признаюсь, давно уже забыл чем причастие отличается от деепричастия, что такое склонение, и многое другое, относящееся к терминам правил русского языка. Но это не мешает мне формулировать мысли, понимать вас, и вести беседу. Вопрос: как изменится моя речь, если я начну думать не о смысле сказанного, а о терминах?


        1. Warlock_29A
          16.09.2016 16:32
          +4

          "работа должна быть сделана качественно и в сроки"

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


          И для получения от ФП профита уже сегодня, совершенно не нужно знать что такое функторы, монады, моноиды и другие термины из Теории Категорий. Не ужели вы не используете лямбда функции и хотя бы такие паттерны как map, reduce, filter ?


          Большая часть терминов из этой статьи придумана математики в рамках разработки теории категорий, можете посоветовать им что именно они могли бы сделать полезного, заместо того что бы заниматься всякой ерундой на вроде:


          разложение на категории ради разложения и присвоение терминов ради присвоения


        1. faiwer
          16.09.2016 16:35
          +3

          Есть такое слово: парадигма. Судя по вики это: "совокупность фундаментальных научных установок, представлений и терминов". Т.е. нечто большое, что может повлечь за собой смену образа мышления. Скажем есть декларативность и императивность. Согласитесь, это далеко не одно и тоже. И там и там большая мат. база. И чтобы этим жить "в полный рост" нужно эту базу понимать и уметь применять.


          Вот и с ФП такая же картинка. Если рассматривать отдельные кирпичики из него, возникнет недопонимание: зачем это может быть нужно? Всё так сложно и без явных преимуществ. Но кирпичики комбинируются, соединяются в причудливые схемы. И вот тогда программист пожинает profit. А так как область эта очень большая и развивается десятилетиями, да ещё и сугубо математична (а математика никогда не была особенно простой), то и нечему удивляться, что всё как-то абстрактно и сложно.


          1. greendimka
            16.09.2016 16:44
            -1

            Спасибо за ответ. Ваша точка зрения мне понятна.

            Всё же останусь при своём. А именно: считаю чрезмерное использование терминов излишней тратой времени и ресурсов.
            Безусловно, всем, представленным в статье, парадигмам должны быть обучены все, без исключения, программисты. Но не на уровне терминов, а на подсознательном уровне. Ровно так же, как и с языками, на которых мы общаемся.


            1. faiwer
              16.09.2016 16:53
              +1

              Но не на уровне терминов, а на подсознательном уровне

              Можно ли понять диффуры без подготовки в виде многих лет изучения алгебры? Думаю да. Но какой ценой.


        1. 80x86
          16.09.2016 16:38
          +3

          Прошу заранее прощения за грубость и контрастность того, что будет ниже.

          То есть разложение на категории ради разложения и присвоение терминов ради присвоения

          Это зависит от точки зрения.

          Можно воспринимать ФП (в контексте парадигмы) как бессмысленный набор стрелочек и символов и относиться к этому как к интеллектуальной мастурбации. Можно воспринимать его как онтологию и, соответственно, как радикальный и целостный подход к решению прикладных статических задач.

          Первая точка зрения, в таком случае, автоматически (согласно поведенческой установке, лежащей в её базе) отсекает для носителя целый пласт интеллектуального творчества неглупых людей. Вторая же точка зрения позволяет взглянуть на достижение цели (т.е., сложную динамическую задачу) как на, прошу прощения за аналогию, «интеграл» статических задач по состоянию (стейту) и, соответственно, даёт возможность элегантно отделять декларативные (описательные) элементы достижения поставленной цели от императивных (последовательных и зависимых) элементов. Не думаю, что это плохая возможность.

          Не секрет же, что метапрограммирование, проникшее во многие императивные языки, позволяет более эффективно решать задачи с точки зрения производительности разработчика. Это что, тоже плохо? А полностью овладеть метапрограммированием, без понимания функционального программирования, просто невозможно.

          Вопрос: как изменится моя речь, если я начну думать не о смысле сказанного, а о терминах?


          Никак. Ваша интеллектуальная деятельность уже впитала в себя парадигму русского языка как комплекса синтаксиса, морфологии, семантики и, соответственно, ментальности как онтологии русского языка. Но, без изучения синтаксиса, морфологии, семантики и, как следствие, восприятия ментальности, вы бы не смогли вести беседу на русском языке как русский. При этом, как вы правильно заметили, онтология, как абстракция, может существовать и без её структурного описания. Но, для её рождения это структурное описание нужно.

          Аналогия с функциональным программированием как парадигмой ясна, я думаю?

          P.S. Исправил разметку, но суть текста не менял.


          1. greendimka
            16.09.2016 16:49
            +1

            Спасибо за хороший развёрнутый ответ.

            P.S. жаль, что докапываться до истины всегда приходится сквозь шквал минусов и грубость


            1. 80x86
              16.09.2016 16:55
              +2

              Да не за что.

              Просто вы, в конфронтационной манере типировав, задели тех, для кого эта статья предназначалась. Реакция читателей, думаю, очевидна. Минус поставить проще, чем потратить время на ответ :)


      1. erlyvideo
        16.09.2016 23:50

        Вы абсолютно неправы, а greendimka задал очень важный вопрос: зачем все эти термины и как они помогают программировать?


        1. Labunsky
          17.09.2016 01:27

          Этот вопрос можно спокойно задавать в любой другой статье по любой парадигме. А ответ везде будет один — «кому-то так удобнее»


          1. erlyvideo
            17.09.2016 08:05

            это глупый ответ людей, которые не смогли до конца разобраться в том, что для них придумали.

            Человек, который разобрался, даст условия и покажет: как и когда этим пользоваться.

            Посмотрите Мартина Фаулера Рефакторинг кода: все приемы он сопровождает примерами кода в которых это будет удобно, причем к каждому примеру делает контрпример.


    1. sshikov
      16.09.2016 19:26
      -1

      Знаете, я вот не понял, за что вас заминусовали. На самом деле, очень многие статьи по ФП страдают именно этим — из них нифига не ясно, в чем состоит полезность той или иной парадигмы. Причем ее в общем-то можно сформулировать в нескольких предложениях или максимум абзацах — но почему-то довольно редко получается это сделать. И вопрос ваш вполне имеет право на жизнь, более того — просто обязан быть задан.


      Но в данном случае вы выбрали неудачный объект для претензий. Разделение функций конечно существует, но большого значения ему не придается, по одной простой причине — в большинстве полноценных ФП языков достаточно иметь унарные функции, которые являются сущностью первого порядка, чтобы свести все остальные к ним. И это, кстати, является одним из достоинств — в ФП есть совсем немного фундаментальных сущностей, которые пришли как правило из математики, при этом все остальные сущности можно из них эффективно выразить.


  1. 80x86
    16.09.2016 15:37
    +5

    Демонстрация парадигм ФП на котятах в Javascript-переложении, безусловно, хороша в пропагандистских и общеобразовательных целях, но, к сожалению, чревата импринтингом. Надо бы от этом в комментарию к переводу написать, т.к. монада, например, в общем случае, не есть контракт на chain/of в терминах реализации на Javacript.

    Несмотря на это, перевод стоит добавить в избранное как intelligence mock.


  1. ParkeTg
    16.09.2016 15:55
    +1

    Раз уже речь зашла о ФП на примере JavaScript, хочу обратить внимание общественности на один замечательный проект:
    PureScript. Компилятор генерирует хороший человекочитаемый код.
    Очень похож (практически клон) на Haskell, однако есть некоторые ключевые отличия.
    Более глубоко можно ознакомиться в документе PureScript by Example.


    1. Warlock_29A
      16.09.2016 16:08
      +2

      Тогда можно еще и на Elm обратить внимание.


      1. hellosandrik
        17.09.2016 08:58

        Тысячи их :) Но из всех них к терминам в статье ближе всего именно PureScript, т.к. он ближе всего к Haskell. Конечно, есть еще ghcjs, но это не особо прагматичный подход и тянет за собой большой рантайм (в первую очередь из-за сохранения семантики Haskell, а именно laziness). У PureScript вообще нет рантайма, его очень легко начать использовать в проекте (например, с webpack loader'ом) и сразу получить все плюсы чисто-функционального языка, не отказываясь при этом от богатой экосистемы Node.js (потому что у PureScript супер простой FFI). Что касается Elm, то это скорее «all-or-nothing» решение. Если захочется использовать third-party js библиотеки, то надо быть готовым к тому, чтобы городить огород кода (как js, так и elm). А еще, в отличие от PureScript, который уже достаточно стабилен (он все-таки копирует Haskell), Elm все еще развивается и буквально в недавнем релизе снова поменялся API. В общем, я считаю, что для продакшена он пока не готов, хотя идея довольно интересная. P.S.: Сам уже устал от js, «undefined is not a function», тонны рантайм ошибок, boilerplate'а из Immutable.js и пр., поэтому потратил довольно много времени на выбор альтернативы и остался с PureScript, т.к. это наиболее прагматичный из всех подход.


  1. BiosUefi
    16.09.2016 16:38

    Смотрю многие хвалят функциональное программирование, посоветуйте как его можно приментить для инициализации UEFI. Подойдет любой вариант хоть для x86-64, хоть для ARM.


    1. 80x86
      16.09.2016 16:50
      +7

      «Смотрю, многие хвалят метапрограммирование, паттерны проектирования, контейнеризацию, сервисно-ориентированную архитектуру, посоветуйте, как их можно применить для инициализации UEFI. Подойдёт любой вариант хоть для amd64, хоть для ARM.»

      Советовать сварщику варить металл кисточкой для рисования, я думаю, глупо. Глупо и сварщику о таком спрашивать.


    1. ForNeVeR
      16.09.2016 18:03
      +1

      Я не квалифицирован достаточно, чтобы отвечать на ваш вопрос, но попробую просто в виде примера показать, как ФП может помочь решать задачи из реального мира.


      Есть такая штука — SSIS. По жизни штуковина довольно сложная, и вызывающая множество проблем и боли у разработчиков, которые её пытаются использовать для каких-то прикладных задач (а она действительно бывает нужна, просто поверьте пока что на слово). Итак, разработчики плачут, колются, но продолжают пожирать кактус с полуимперативными действиями по деплойменту и настройке SSIS-пакетов, воюют с разными версиями IDE, серверов и прочей дребеденью.


      И тут появляется Chimayo. С использованием сравнительно небольшого количества паттернов функционального языка она позволяет описывать эти SSIS-задачи, формировать их в функциональном стиле, манипулировать как значениями первого класса, ну и всё такое. В общем, позволяет избавиться от боли и начать программистам, наконец, программировать.


      К чему я это сказал? Я думаю, что для вашей инициализации UEFI просто не написан (или пока что вам не встретился) подходящий декларативный инструмент, использующий современные достижения функционального программирования. Если б он был — поверьте, вам бы понравилось, как и нам понравилось работать с Chimayo после ада ручной настройки SSIS.


  1. BiosUefi
    16.09.2016 16:59
    +1

    «Глупо и сварщику о таком спрашивать. „

    Ну вот опять рабочий класс послали, а так хотелось приобщиться к прекрасному! Не “стрелять себе а ногу», манипулировать обьектами и функциональностью, наслаждаться шаблонами и паттернами.

    Увы и ах, ухожу плакать.


    1. 80x86
      16.09.2016 17:12
      +3

      А почему вы уходите плакать? В каждом технологическом стеке есть слои, внутри которых существуют специализированные инструменты, зона применимости которых ограничена этими слоями и какой-то пограничной их областью. Узость пограничной область, в свою очередь, определяется обратной степенной функцией комбинаторной сложности проникающих между слоями решений. Это обуславливается накладными расходами компрессии механики описания прикладной области слоя (т.е. мощностью абстракции) и, соответственно, ресурсами, необходимыми для компенсации этих накладных расходов.

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


    1. eugzol
      16.09.2016 19:59
      +1

      Вообще, ответы на ваши вполне корректные вопросы показывает всю несостоятельность местных икспертов по ФП, надувающих здесь щёки. Если есть решения на функциональных ЯП, решающих задачи конвертации контейнеров видеопотоков, почему бы оно было априори не применимо к другим низкоуровневым задачам? Я не эксперт в ФП (хотя тут ни один комментатор каких-то надёжных свидетельств своей экспертности не привёл), но мне кажется, что ФП не применяют к низкоуровневым задачам из-за сложившейся привычки и отсутствия должной инфраструктуры (компиляторов, стандартных библиотек с нужными структурами данных, и т.д.), а не из-за какой-то принципиальной невозможности.

      Собственно, ФП практически нигде не применяют по этой причине. Инерция мышления и наличие огромной императивной базы кода.

      Кстати, Javascript, каким бы он уродским языком не был (я сам его таковым считаю) и как бы не кривили морды парящие в абстракных функторо-монадных высях завезённые эксперты, как раз за счёт своей огромной популярности по факту подогревает интерес к теме.


      1. lorc
        16.09.2016 22:02
        +1

        ФП хороши для преобразования данных. Собственно, функции в математическом смысле только и делают что отображают элементы одних множеств на элементы других множеств. Идеальная программа на ФП не содержит сайд-эффектов.

        Напротив, вся работа железа на низком уровне построена именно на сайд-эффектах. Настолько, что это даже сносит крышу даже обычным С-компиляторам. Например как вам такое — вы пишете в область памяти одно число, а потом читаете оттуда другое? Или читаете два раза подряд из одной и тоже ячейки памяти и при этом получаете совершенно разные значения?
        Это не глюк, так работает вся memory-mapped периферия. Приходится использовать хитрые конструкции чтобы сишные компиляторы не ломали работу с периферией своими оптимизациями. Более того, нынешние процессоры тоже шибко умные и выбрасывают лишние (по их мнениею) обращения к памяти. А что тогда говорить про языки более высокого уровня? Поэтому и нет применеия ФП в UEFI.

        Зато, если мне надо будет превращать тысячи HTTP запросов в тысячи HTTP ответов — я вользу Эрланг. Потому что ключевое слово — «превращать». Отображать множество HTTP запросов на множество HTTP ответов.


        1. hellosandrik
          17.09.2016 09:48

          Мне кажется, что то, как сейчас построена работа железа на низком уровне вовсе не означает, что применения ФП там нет и быть не может. Просто так сложилось — мы сделали императивное железо и начали писать императивный код. Но если применить принципы ФП в нужном месте, то можно получить большой плюс, даже в отношении железа. Я считаю, что яркий пример — шейдеры. Раньше компьютерная графика была довольно слабой, но распараллелив вычисления удалось достигнуть большого прироста производительности. А распараллелить удалось применив принципы ФП. Ведь шейдеры, по сути — функции без сайд-эффектов: мы передаем данные в вершинный шейдер, а результат вычисления в фрагментный шейдер. Так что, с «инерцией мышления» я очень согласен.


      1. xGromMx
        16.09.2016 22:59

        Netflix полностью построили свой бизнес на RxJava и RxJS что используется у них в проде. От части в Rx есть некие элементы FP


      1. seryh
        17.09.2016 08:58

        Пример Clojure показывает что наличием должной инфраструктуры, популяризации должной не добиться. Там через interop в java, доступны все богатства и мощь JVM мира. Но как видим, только небольшая прослойка ценителей ФП осторожно работают с ним или со Scala. Так что инерция мышления, самый главный враг ФП.


    1. lorc
      16.09.2016 22:05
      +1

      А потому что для каждой задачи свой инструмент. Я вот тоже по работе занимаюсь тем, что битики флипаю. Но когда мне надо обработать мегабайт трейс-логов я достаю Питон, а не начинаю писать парсер на С. Угадайте, почему?


  1. xGromMx
    16.09.2016 17:28

    В школе нас учат арифметики, операции над числами что есть некое множество которое поддается законам моноида. Но если обобщить все это и перенести на различный уровень абстракций то это правило будет справедливо и для других абстракций. Вот в программировании ТК это есть некая категория типов, а морфизмы это ф-ции вида a->b. Также эти ф-ции являются моноидами ибо есть единичный элемент id = x->x и бинарный оператор композиция. Функтор это тоже морфизм просто между 2-категориями (грубо говоря если вы простой тип упакуете в другой, например int в array). Задача ТК научится манипулировать между абстракциями не взирая на содержимое. Если был массив то после морфизма это и должен быть массив. Не важно был это массив целых, а стал массив строк, но ведь условие сохранения внешней структуры осталось это все тот же массив. В древнем Египте все начиналось с того, что люди рисовали 10,20,300 различных объектов (людей, и прочее). Но вскоре они поняли, что всему этому можно придать некую абстрактность и что 2 человека + 1 человек будет 3 и это справедливо к пирамидам, золоту и тд. Далее понимается тот факт что помимо простой манипуляции это все можно объединить в некий класс (множество) и иметь все те же законы (вот например в школе мы учили всякие коммутативные, ассоциативные, дистрибутивные, левая/правая тождественность и прочее) И в чем парадокс, что все эти законы ложаться на любой уровень абстракции. Вот например для моноида работают законы ассоциативности, левой и правой тождественности ну иногда (например как с числами, а более того у них два бинарных оператора типа +/* и два возможных множества, это множество целых и натуральных чисел) закон коммутативности. Об этом можно продолжать еще долго, просто нужно понять одно те, маленькие кирпичики что мы знали раньше при определенных условиях мы можем применять на различных структурах, если поддать их неким общим математическим правилам и законам. И тогда (в отличии от императивных подходов) мы можем строго уверять, что поведение будет строгим и закономерным. Думаю все рассказать в одном комментарии трудно =)


  1. erlyvideo
    16.09.2016 18:55

    const map = (fn) => (list) => list.map(fn)
    const add = (a) => (b) => a + b

    Совершенно непонятная запись. Что здесь вообще написано?



    1. xGromMx
      16.09.2016 19:48
      +2

      еще для одного аргумента скобки можно опустить и будет все читаемей)

      const S = x => y => z => x(z)(y(z))
      const K = x => y => x
      const I = S(K)(K)


  1. G-M-A-X
    16.09.2016 22:38

    Вот что мне не нравится в ООП, так то, что там методы объектов имеют побочные эффекты (влияют на члены объекта).

    Ну и вообще не люблю трехэтажные абстракции.

    Сам я не фанат ни одной из парадигм.
    Абстракции повышаю по мере надобности.


  1. erlyvideo
    16.09.2016 23:54
    -2

    Рахим, прости, но я поставил минус.

    Мы зарабатываем деньги на разработке и продаже софта на эрланге, но мне ничего непонятно из этой статьи, я ощутил себя так же беспомощно, как при чтении книжки Душина и так же без понимания того: зачем вся эта терминология, какие проблемы решает и как.

    Плюс это всё таки статья для тех, кто уже знает хаскель, а для них она мягко говоря бессмысленна, они уже итак всю терминологию знают.

    Хотите сделать статью не для любителей хаскеля — используйте ну хотя бы яваскрипт что ли, но ни в коем случае не хаскелевский синтаксис.


    1. gearbox
      12.01.2017 20:44

      Это у Вас совсем сурово молодость прошла. Я то уже тиристоры успел застать )


      1. erlyvideo
        17.09.2016 08:06

        const liftA2 = (f) => (a, b) => a.map(f).ap(b)


        Да, конечно


        1. arvitaly
          17.09.2016 08:35

          Это действительно JavaScript. Правда, в примере ошибка, но не в синтаксисе.
          https://habrahabr.ru/company/plarium/blog/270353/


          1. erlyvideo
            17.09.2016 10:15

            тихий ужас


  1. yamatoko
    17.09.2016 05:57

    в чем разница между каррированием и частичным применением функции?


    1. Optik
      17.09.2016 08:23

      Каррирование — преобразование сигнатуры функции, для получения результата нужно передать то же количество аргументов. После каррирования получается функция, которая принимает часть аргументов и возвращает новую функцию, которая принимает другую часть аргументов и возвращает…

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

      Каррирование и частичное применение связаны непосредственно друг с другом, поэтому это нормально, что не видно сначала разницы.


    1. e_Hector
      17.09.2016 08:59

      насколько понял из статьи, каррированная функция всегда принимает строго один аргумент и возвращает либо результат, либо функцию для следующего аргумента, а частично примененная функция просто принимает меньшее число аргументов чем изначальная функция и сразу возвращает результат.


      1. yamatoko
        17.09.2016 09:18

        а автоматическое каррирование?