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

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

предисловие

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

Определение функций высшего порядка

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

Определение функции высшего порядка:

Функции можно передавать в качестве параметров
Функции могут быть выведены как возвращаемые значения

Напишите код, используя функции высшего порядка, которые поставляются с ES6.

Предположим, у нас есть такой массив:

const classA = [
    {
        name: '张三',
        age: 17
    },
    {
        name: '李四',
        age: 15
    },
    {
        name: '王五',
        age: 16
    },
]

Требуется найти 16-летних учеников в классе, мы используем функции младшего порядка для фильтрации следующим образом:

let student = [];
for (let i = 0; i < classA.length; i++) {
    if (classA[i].age === 16) {
        student.push(class[i])
    }
}

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

const student = classA.filter( v => v.age === 16 )

Итак, каковы преимущества использования таких функций более высокого порядка?Есть два момента:

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

Например, такую ​​функцию для фильтрации студентов можно разделить на две части:

const isAge = v => v.age === 16;
const result = classA.filter(isAge);

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

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

Что ж, некоторые люди могут сказать, что это слишком просто, так что сделайте это немного сложнее!

Предположим, у нас есть такой массив:

const array = [['张三','26','1000'],['李四','25','3655'],['王五','30','8888']]

Мы собираемся превратить этот массив во что-то вроде этого:

[
    {
        name: '张三',
        age: '26',
        price: '1000'
    },
    {
        name: '李四',
        age: '25',
        price: '3655'
    },
    {
        name: '王五',
        age: '30',
        price: '8888'
    },
]

Используйте функции высшего порядка для преобразования:

const result = array.reduce((value, item, index) => {
  value[index] = {
    name: item[0],
    age: item[1],
    price: item[2]
  };
  return value;
}, []);

Здесь мы используем функции высшего порядка ES6.reduce, для конкретного ознакомления вы можете посетить Bump Lab.Неполное руководство по методу Reduce() в JavaScript

Функции высшего порядка, поставляемые с ES6,filter,map,reduceИ т. д. и т. д.

Хорошо, на данный момент у меня есть несколько простых концепций функционального программирования.Я понимаю функциональное программирование:

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

Напишите свою собственную функцию высшего порядка

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

Проще говоря, throttling function — это функция, которая управляет частотой срабатывания событий, раньше она могла срабатывать неограниченное количество раз в течение одной секунды, а теперь — срабатывает только раз в 500 миллисекунд.

throttle(fn, wait=500) {
    if (typeof fn != "function") {
        // 必须传入函数
        throw new TypeError("Expected a function")
    }
    
    // 定时器
    let timer,
    // 是否是第一次调用
    firstTime = true;
    
    // 这里不能用箭头函数,是为了绑定上下文
    return function (...args) {
        // 第一次
        if (firstTime) {
            firstTime = false;
            fn.apply(this,args);
        }
        
        if (timer) {
            return;
        }else {
            timer = setTimeout(() => {
                clearTimeout(timer);
                timer = null;
                fn.apply(this, args);
            },wait)
        }

    }
}

// 单独使用,限制快速连续不停的点击,按钮只会有规律的每500ms点击有效
button.addEventListener('click', throttle(() => {
    console.log('hhh')
}))

Написав такую ​​функцию высшего порядка, мы можем вызывать ее везде, например:

// 有一个点击增加的功能,但是要求最少过了1秒才能增加一次,就可以
const add = x => x++;
throttle(add,1000);

// 又有了一个减少的功能,但是要求最少2秒减少一次
const cutDown = x => x--;
throttle(cutDown,2000);

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

чистая функция

В концепции функционального программирования еще одним важным понятием является чистая функция, так что же такое чистая функция?

Давайте используем код, чтобы объяснить, что такое чистая функция:

const z = 10;
add(x, y) {
    return x + y;
}

надaddФункция — это чистая функция, которая считываетxа такжеyзначение двух аргументов, возвращает их сумму и не зависит от глобальногоzвлияние переменных

изменить эту функцию

const z = 10;
add(x, y) {
    return x + y + z;
}

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

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

Итак, мы получили первое важное основание для суждения о том, является ли чистая функция

1. Чистые функции не зависят от внешней среды.

повторное использованиеspliceа такжеsliceОбъяснить:

var xs = [1,2,3,4,5];

// 纯的
xs.slice(0,3);
//=> [1,2,3]

xs.slice(0,3);
//=> [1,2,3]

xs.slice(0,3);
//=> [1,2,3]


// 不纯的
xs.splice(0,3);
//=> [1,2,3]

xs.splice(0,3);
//=> [4,5]

xs.splice(0,3);
//=> []

sliceОна получает одни и те же параметры и каждый раз возвращает одно и то же значение, поэтому это чистая функция.

spliceОна получает одни и те же параметры и каждый раз возвращает разные значения, поэтому это не чистая функция.

Итак, мы приходим ко второй важной основе для судейства или нет

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

Подводя итог, чистые функции:

'纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用'

Так что же副作用, в чистой функции есть такое определение:

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

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

К этому моменту я наконец-то понял, что такое чистая функция, и у нее есть следующие преимущества:

  • легче тестировать, потому что их единственная работа — вычислять выходные данные на основе входных данных.
  • Результаты могут быть кэшированы, потому что один и тот же ввод всегда будет получать один и тот же вывод.
  • Самодокументируемость, так как зависимости функции понятны.
  • Легче вызывать, потому что вам не нужно беспокоиться о побочных эффектах функции.

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

карри

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

const add = x => y => x + y;
add(1)(2);
// => 3

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

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

Напишите каррированную функцию, используя приведенные выше идеи.

// 创建柯里化函数,保存了第一次传入的参数和函数,返回值是一个函数并且接收第二次传入参数,同时调用传入的函数进行计算
currying (fn, ...args1) {
    return (...args2) => {
        return fn(...args1, ...args2)
    }
}

// 定义一个一般函数
const add = (x, y) => x + y;

// 使用
const increment = currying(add, 1);
console.log(increment(2));
const addTen = currying(add, 10);
console.log(addTen(2));

// => 3
// => 12

В этом примере все еще есть небольшая проблема, то есть возвращаемое значение не каррируется автоматически и может быть изменено:

currying(fn, ...args1) {
  // '判断传入的参数是否满足传入函数需要的参数,比如说add函数需要两个参数相加,那么判断是否传入了两个参数,满足调用传入函数计算结果'
  if (args1.length >= fn.length) {
    console.log(args1, '--1--');
    return fn(...args1);
  }
  // '不满足返回一个新的函数,继续调用柯里化函数,传入保存的第一次传入的函数,传入保存的第一次传入的参数,传入第二次传入的参数,继续上面的判断逻辑,返回计算结果'
  return (...args2) => {
    console.log(args2, '--2--');
    return currying(fn, ...args1, ...args2);
  };
},

// 定义一个一般函数
const add = (x, y) => x + y;

// 使用
const increment = currying(add, 1);
console.log(increment(2));
const addTen = currying(add, 10);
console.log(addTen(2));

// => [2] --2--
// => [1,2] --1--
// => 3
// => [2] --2--
// => [10,2] --1--
// => 12

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

const add = (x, y) => x + y;
console.log(add.length)
// => 2

В ES6,...является оператором спреда, его использование похоже на это

// 放在函数作为单独参数,会把一个数组变成参数序列,比如上面例子中的数组[1,2]变成了参数x=1,y=2
fn(...args1)

// 放在函数中作为第二个参数,会把传入的值变成一个数组,如果传入的是一个数组那么还是数组,传入一个对象,会变成一个数组对象
function currying(fn,...x) {
	console.log(x)
}
currying(0,1)
// => [1]

// 放在回调函数中作为第二个和第三个参数
// 第一次调用会返回一个函数,会在闭包里存贮值,第二次调用会把闭包里的值和第二次参数里的值合并成数组
return currying(fn, ...args1, ...args2);
// => [1,2]

// 但是单独在函数中这么使用会报错
function currying(fn,...x,...y) {
	console.log(x)
}
currying(0,1,2)

Поймите это, приведенный выше пример легко понять.

Наиболее важными идеями каррирования функций являются:

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

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

Комбинации кодов

Во-первых, напишите простую функцию композиции:

const compose = (f, g) => x => f(g(x));

Эта составная функция принимает две функции в качестве аргументов и возвращает новую функцию, где x — это значение, которое будет использоваться между двумя функциями, например:

// 我们要实现一个给字符串全部变成大写,然后加上个感叹号的功能,只需要定义两个函数,然后组合一下
const toUpperCase = x => x.toUpperCase();
const exclaim = x => `${x}!`;
const shout = compose(exclaim, toUpperCase);

shout('hello world')
// => HELLO WORLD!

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

Эта комбинированная функция все еще немного проблематична. Она может принимать только 2 параметра. Давайте сделаем небольшую модификацию, чтобы сделать ее сильнее:

const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => [fn.call(null, ...res)], args)[0];

// 使用,实现一个功能,字符串变成大写,加上个感叹号,还要截取一部分,再在前面加上注释
const toUpperCase = x => x.toUpperCase();
const exclaim = x => `${x}!`;
const head = x => `slice is: ${x}`;
const reverse = x => x.slice(0, 7);

const shout = compose(exclaim, toUpperCase, head, reverse)
shout('my name is maya')
// => SLICE IS: MY NAME!

Принцип комбинации на самом деле является ассоциативным законом в математике:

(a + b) + c  =  a + (b + c)

Итак, в сочетании вы можете сделать это

// 第一种
const one = compose(exclaim, toUpperCase)
const shout = compose(one, head, reverse)
shout('my name is maya')
// => SLICE IS: MY NAME!

// 第二种
const two = compose(toUpperCase, head)
const shout = compose(exclaim, two, reverse)
shout('my name is maya')
// => SLICE IS: MY NAME!

// 第三种
const three = compose(head, reverse)
const shout = compose(exclaim, toUpperCase, three)
shout('my name is maya')
// => SLICE IS: MY NAME!

...

Итак, на данный момент мое понимание комбинации таково:

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

Есть комбинированные функции в разных библиотеках,lodash,underscore,ramdaждать, например, вunderscoreВнутри состав выглядит так:

  // Returns a function that is the composition of a list of functions, each
  // consuming the return value of the function that follows.
  _.compose = function() {
    var args = arguments;
    var start = args.length - 1;
    return function() {
      var i = start;
      var result = args[start].apply(this, arguments);
      while (i--) result = args[i].call(this, result);
      return result;
    };
  };

В сочетании с

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

// 伪代码,思路
// 比如说,我们请求后台拿到了一个数据,然后我们需要筛选几次这个数据, 取出里面的一部分,并且排序

// 数据
const res = {
    status: 200,
    data: [
        {
            id: xxx,
            name: xxx,
            time: xxx,
            content: xxx,
            created: xxx
        },
        ...
    ]
}

// 封装的请求函数
const http = xxx;

// '传统写法是这样的'
http.post
    .then(res => 拿到数据)
    .then(res => 做出筛选)
    .then(res => 做出筛选)
    .then(res => 取出一部分)
    .then(res => 排序)
    
// '函数式编程是这样的'
// 声明一个筛选函数
const a = curry()
// 声明一个取出函数
const b = curry()
// 声明一个排序函数
const c = curry()
// 组合起来
const shout = compose(c, b, a)
// 使用
shout(http.post)

Как официально использовать функциональное программирование в своих проектах

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

  • 1. Сначала попробуйте использовать функции более высокого порядка, которые поставляются с ES6.
  • 2. После того, как вы ознакомитесь с функциями высшего порядка, которые поставляются с ES6, вы можете попробовать написать несколько функций более высокого порядка самостоятельно.
  • 3. В этом процессе попробуйте использовать чистые функции для записи кода
  • 4. Как только у вас появится некоторое представление о функциональном программировании, попробуйте использовать что-то вродеramdaбиблиотека для написания кода
  • 5. В использованииramdaВ процессе можно попробовать изучить его исходный код
  • 6. Попробуйте написать собственную библиотеку, каррировать функции, комбинировать функции и т. д.

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

不要为了函数式而选择函数式编程。 If the function programming can help you, you can upgrade the efficiency, quality, you can use; if you can't, then don't use it; if you are still not familiar with the function, such as what I like, occasionally использовать

расширять

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

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

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

наконец

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

Ссылаться на

Инкапсуляция функций дросселирования и устранения дребезга с помощью функционального программирования
Изучите функциональное программирование на JavaScript (часть 1)
Mostly adequate guide to FP
Разумное использование чистого функционального программирования
Введение в функциональное программирование
Здоровяк, каррирование JavaScript, понятно?