Когда уместно выбирать стиль без точек по сравнению с стилем, ориентированным на данные, в функциональном программировании? - PullRequest
0 голосов
/ 07 декабря 2018

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

Хотя все на работе полностью приняли функциональное программирование, есть такжемного дискуссий о том, как сделать это «правильно».

Эти две функции будут делать одно и то же: взять список и вернуть новый список, в котором все строки были обрезаны.

// data-centric style
const trimList = list => R.map(R.trim, list);
// point-free style
const trimList = R.map(R.trim);

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

var opts = {a: 'foo', b: 'bar', c: 'baz'}; 
var list = ['foo', 'foo', 'bar', 'foo', 'baz', 'bar'];

myFilter(opts, 'a', list); //=> ["foo", "foo", "foo"]
myFilter(opts, 'b', list); //=> ["bar", "bar"]
// data-centric style
const myFilter = (opts, key, list) => {
  var predicate = R.equals(opts[key]);
  return R.filter(predicate, list);
};
// point-free style
const myFilter = R.converge(
  R.filter, [
    R.converge(
      R.compose(R.equals, R.prop), [
        R.nthArg(1),
        R.nthArg(0)]),
    R.nthArg(2)]);

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

Ответы [ 3 ]

0 голосов
/ 12 декабря 2018

Я не знаю доказательств, демонстрирующих преимущества одного стиля над другим.Но в истории программирования есть четкая тенденция к более высоким абстракциям ... и столь же ясная история сопротивления этой тенденции.Переход от Assembly к Fortran или LISP был движением вверх по стеку абстракций.Использование SQL, а не заказное извлечение B-дерева было другим.Переход к FP как внутри языка, такого как Javascript, так и в изменяющейся среде языков программирования, на мой взгляд, аналогичен.

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

Тем не менее, это часто проще.И это важно.Более простой код легче читать, его легче модифицировать.Обратите внимание, что я различаю simple и easy - различие, сформулированное в классическом выступлении Rich Hickey .Те, кто плохо знаком с этим стилем, часто находят его более запутанным;как и программисты на ассемблере, которые ненавидели следующее поколение языка и все такое.

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

Трудно утверждать, что это:

const foo = (arg) => {
  const qux = baz(arg)
  return bar(qux)
}

или даже это:

const foo = (arg) => bar(baz(arg))

проще, чем это:

const foo = compose(bar, baz)

И это потому, что все три включаютэти понятия:

  • объявление функции
  • ссылка на функцию

второе также добавляет:

  • определение аргумента
  • тело функции
  • приложение функции
  • вложенность вызовов функций

, и первая версия имеет:

  • определение аргумента
  • тело функции
  • приложение функции
  • определение локальной переменной
  • назначение локальной переменной
  • оператор return

в то время как третий добавляет только

  • композиция функций

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


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

Но не нужно удалять каждую точку, чтобы, к сожалению, доказать точку.Легко попасть в ловушку, пытаясь сделать все бессмысленным только потому, что вы можете.Мы уже знаем, что это возможно;нам не нужно видеть кровавые подробности.

0 голосов
/ 24 декабря 2018

есть несколько хороших ответов, и, по моему мнению, смешивание двух стилей - это путь.

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

const myFilter = converge(
  filter,
  [compose(equals , flip(prop)) , nthArg(2)]
 )
0 голосов
/ 07 декабря 2018

Академический термин эта конверсия .Если у вас есть функция с избыточной лямбда-абстракцией, такая как

const trim = s => s.trim();
const map = f => xs => xs.map(x => f(x));

const trimList = xs => map(trim) (xs); // lambda redundancy

, вы можете просто удалить последнюю абстракцию лямбды путем сокращения eta:

const trimList = map(trim);

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

На самом деле, есть по крайней мере две причины использовать эта абстракция (противоположная эта сокращение ) в Javascript:

  • для исправления много аргументных функций Javascript, как я делал с map = f => xs => xs.map(x => f(x))
  • , чтобы предотвратить немедленную оценку выражений / операторов (эффект ленивого вычисления), как в recur = f => x => f(recur(f)) (x)
...