Функциональное программирование: раскрытие возможностей этого набора инструментов.

После того, как мы увидели, что означает функциональное программирование и как оно относится к функциям, как ООП относится к объектам (назначение, передача в качестве параметра и возврат из другой функции), мы углубимся в его практическое применение в этом посте и предстоящие. Не стесняйтесь просматривать предыдущие сообщения здесь:





Чистые функции

Прежде чем мы углубимся в преимущества FP, я хочу поговорить о важном термине: Чистая функция, чистая функция - это функция, реализующая эти две концепции:

  1. Один и тот же ввод всегда будет давать один и тот же вывод:
    пример нарушения этого правила: функция, которая принимает целое число и возвращает его с суммой случайных чисел от 1 до 25, это означает передачу числа 100 для этой функции каждый раз будет давать разные результаты.
  2. Не вызывает побочных эффектов:
    пример нарушения этого правила: функция проверяет учетные данные для входа и возвращает, действительны они или нет, но в качестве побочного эффекта генерирует токен и сохраняет это в базе данных, эта функция больше не является чистой, и вызов ее много раз для одного и того же ввода внесет изменения в базу данных.

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

sum(int first,int second) { first+second }

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

Абстракция позволяет языку быть более эффективным

Разработчики языков, поддерживающих функциональную парадигму, обычно предлагают множество общих функций высшего порядка, таких как map, foreach, filter и fold. благодаря языку разработчики могут видеть низкоуровневый код языка, они могут оптимизировать использование этих и других функций, как они это сделали с оптимизацией механизма сборщика мусора.
Например, если я хочу распараллелить сопоставление процесс в нефункциональном программировании. Я должен вдаваться в подробности многопоточности и ее головной боли, но для функциональных языков программирования мы уступаем эту ответственность разработчикам языков, большинство функциональных языков программирования предоставляют способ легкого параллельного запуска функционального кода.< br /> Например: Java начал внедрять FP в Java 8, и он предоставил parallelStream, который сообщает компилятору, что я хочу выполнять эти операции параллельно, давайте посмотрим на этот пример:

public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // Using parallelStream to parallelize mapping
        numbers.parallelStream()
                .map(number -> number * 2)
                .forEach(number -> System.out.println(number));
}

В этом примере мы запускаем сопоставление одновременно, и метод foreach выводит результаты в непредсказуемом порядке в соответствии с параллелизмом.

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

Классифицируйте проблемы по-разному, видя общие черты

Если вы заметили map, filter, reduce, find и многие другие операции, они являются решениями для общих проблем, абстрагирование общих проблем — это очень известный подход в FP.
Представьте, что у вас есть проблема A и проблема B. , в обеих есть фильтрация списка данных по определенному условию, в ООП вы можете реализовывать каждую задачу самостоятельно и решать ее, но в ФП вы можете заметить, что в обеих задачах есть операция «фильтрации», но условие другое, так что я могу абстрагироваться от общности фильтрации и сделать условие параметром, и использовать его в обеих задачах.
И когда вы погружаетесь глубже в ФП и используете его все больше и больше, способ, которым вы думаете о решении проблем, будет другим. .

Закрытия

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

function createSalaryPredicate(minSalary) {
  return function(employee) {
    return employee.salary >= minSalary;
  }
}

const isHighPaid = createSalaryPredicate(100000);
const isWellPaid = createSalaryPredicate(70000);

const employees = [
  { name: 'John', salary: 90000 },
  { name: 'Alice', salary: 110000 },
  { name: 'Bob', salary: 80000 }
];

const highPaidEmployees = employees.filter(isHighPaid);
const wellPaidEmployees = employees.filter(isWellPaid);

console.log(highPaidEmployees);
console.log(wellPaidEmployees);

Котлин:

data class Employee(val name: String, val salary: Int)

fun createSalaryPredicate(minSalary: Int): (Employee) -> Boolean {
    return { employee -> employee.salary >= minSalary }
}

val isHighPaid = createSalaryPredicate(100000)
val isWellPaid = createSalaryPredicate(70000)

val employees = listOf(
    Employee("John", 90000),
    Employee("Alice", 110000),
    Employee("Bob", 80000)
)

val highPaidEmployees = employees.filter(isHighPaid)
val wellPaidEmployees = employees.filter(isWellPaid)

println(highPaidEmployees)
println(wellPaidEmployees)

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

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

function createIncrementer() {
  let count = 0;

  function increment() {
    count++;
    console.log(count);
  }

  return increment;
}

const incrementer1 = createIncrementer();
const incrementer2 = createIncrementer();

incrementer1(); // Output: 1
incrementer1(); // Output: 2
incrementer1(); // Output: 3

incrementer2(); // Output: 1
incrementer2(); // Output: 2
incrementer2(); // Output: 3
fun createIncrementer(): () -> Unit {
    var count = 0

    return {
        count++
        println(count)
    }
}

val incrementer1 = createIncrementer()
val incrementer2 = createIncrementer()

incrementer1() // Output: 1
incrementer1() // Output: 2
incrementer1() // Output: 3

incrementer2() // Output: 1
incrementer2() // Output: 2
incrementer2() // Output: 3

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

Захватывайте контекст, а не состояние.

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

Я думаю, что для этого поста достаточно, в следующем посте мы углубимся в преимущества, которые делают код лучше и повышают читаемость, спасибо, что прочитали все это, я надеюсь, что это что-то изменило в ваших знаниях, удачного кодирования 😍🧑‍ 🏻‍💻.