Функциональное программирование: раскрытие возможностей этого набора инструментов.
После того, как мы увидели, что означает функциональное программирование и как оно относится к функциям, как ООП относится к объектам (назначение, передача в качестве параметра и возврат из другой функции), мы углубимся в его практическое применение в этом посте и предстоящие. Не стесняйтесь просматривать предыдущие сообщения здесь:
Чистые функции
Прежде чем мы углубимся в преимущества FP, я хочу поговорить о важном термине: Чистая функция, чистая функция - это функция, реализующая эти две концепции:
- Один и тот же ввод всегда будет давать один и тот же вывод:
пример нарушения этого правила: функция, которая принимает целое число и возвращает его с суммой случайных чисел от 1 до 25, это означает передачу числа 100 для этой функции каждый раз будет давать разные результаты. - Не вызывает побочных эффектов:
пример нарушения этого правила: функция проверяет учетные данные для входа и возвращает, действительны они или нет, но в качестве побочного эффекта генерирует токен и сохраняет это в базе данных, эта функция больше не является чистой, и вызов ее много раз для одного и того же ввода внесет изменения в базу данных.
Чистые функции реагируют только на ввод и производят тот же вывод, и это ничего не меняет снаружи. Простой пример этого:
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 замыкания используются очень редко, но я уверен, что есть случаи, когда замыкание — идеальный способ справиться с ними.
Я думаю, что для этого поста достаточно, в следующем посте мы углубимся в преимущества, которые делают код лучше и повышают читаемость, спасибо, что прочитали все это, я надеюсь, что это что-то изменило в ваших знаниях, удачного кодирования 😍🧑 🏻💻.