Сьогодні простий пост, ідеальний, щоб почитати зранку, поки ви п’єте каву. Ніяких складних концепцій (напевно), тільки мій досвід у формі потоку думок, який ніжно порізаний на абзаци для вашого прискіпливого читання.
Я декілька років працюю джавістом, тобто всякого побачив. Різні проекти, різні технології, але джава завжди мене супроводжувала, хоч з неї я не починав свою подорож у світі інженерії. Незважачаючи на те, що джава не була моєї першої мовою, я однаково мислю в її парадигмі, коли мені потрібно розв’язати задачку. В ній є свої безумовні переваги і мінуси, про які ви точно чули. Наприклад всі чули про: “Write once, run everywhere“ і разом з тим “Java is so verbose…“. Для мене джава ніколи не була verbose (Джаву до лямбда-виразів ми просто ігноруємо), вона саме настільки детальна, наскільки має бути. Мені спокійно від того, що мінімум речей відбувається “під капотом“ і що мені надають стільки контролю скільки потрібно. Коли я програмую на Котліні, або на Пайтоні, то я боюсь зазирнути в ліст для інтеджерів і побачити там ромський табір, або випадково втопитись у меморі ліках.
You can't do a loop if you care about performance in python @John Carmack
В джава є лямбда-вирази і це те місце де все почалось. Лямбда-вирази добавляють у джаву того функціонального смаку, якого їй довгий час бракувало (ігноруємо, що під капотом свторюються анонімні класи). Саме ця функціональність дозволяє писати чистий, читабельний і чарівний код в джаві.
Було:
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(new EvenPredicate())
.forEach(new NumberConsumer());
}
static class EvenPredicate implements java.util.function.Predicate<Integer> {
@Override
public boolean test(Integer number) {
return number % 2 == 0;
}
}
static class NumberConsumer implements java.util.function.Consumer<Integer> {
@Override
public void accept(Integer number) {
System.out.println(number);
}
}
}
Стало:
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
}
}
Було не чарівно, а стало чарівно.
У своїх програмах я стараюсь писати локально функціонально і об’єктно-орієнтовано (якщо так можна сказати) глобально. Тобто я використовую абстракції і принципи SOLID для побудови надійних і довгоживучих ієрархійних систем, а функціональне програмування для швидкої і зручної імплементації.
Приклади використання функціонального програмування локально
Патерн білдер
public class Main {
public static void main(String[] args) {
Person person = new Person(p -> {
p.setName("John");
p.setAge(30);
p.setAddress("123 Main St");
});
System.out.println(person);
}
}
Або
public class Main {
public static void main(String[] args) {
Person person = Person.builder()
.name("John")
.age(30)
.address("123 Main St")
.build();
System.out.println(person);
}
}
HTTP Server
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Main {
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext("/hello", exchange -> {
String response = "Hello, world!";
exchange.sendResponseHeaders(200, response.getBytes().length);
exchange.getResponseBody().write(response.getBytes());
exchange.getResponseBody().close();
});
server.createContext("/greet", exchange -> {
String name = exchange.getRequestURI().getQuery().split("=")[1];
String response = "Hello, " + name + "!";
exchange.sendResponseHeaders(200, response.getBytes().length);
exchange.getResponseBody().write(response.getBytes());
exchange.getResponseBody().close();
});
server.start();
System.out.println("Server is running on port 8000");
}
}
Написання тестів
public class Main {
public static void main(String[] args) {
TestResult result = TestRunner.start()
.setInit(log -> log.append("Initializing test environment...\n"))
.checkA(tr -> tr.toString())
.checkB(tr -> tr.toString())
.clear();
System.out.println(result);
}
}
У тестах це може бути дуже корисно, якщо у вас наприклад величезний проект і вам потрібно писати великі, конфігуровані тести, то такий підхід написання у функціональному стилі, зробить їх читабельними і дозволятиме швидко писати.
Що робить програмування функціональним?
В основному я розглядав стиль написання, але не сильно згадував, що такого особливого має бути в коді, щоб його можна було назвати функціональним. Колись мені це було настільки цікаво, що аж затянуло місяць дивитись відео по теорії категорій і пробувати щось писати на Haskell.
https://www.youtube.com/watch?v=I8LbkfSSR58&list=PLbgaMIhjbmEnaH_LTkxLI7FMa2HsnawM_
Я опираюсь спокусі посвятити ще один пост похвалі математиці, тому просто продовжу.
Власне програмуванян робить функціональним те, що основним юнітом з яким ми працюємо є функція, якщо передавати аргумент - функція, якщо приймати - функція (Функції вищого порядку), таким чином ми відходимо від стейту подальше. Саме відсутність стейту дозволяє створювати чисті функції, які дають Haskell таку перевагу в швидкості обчислень. У функціональному прорамуванін все крутиться навколо функцій.
Then, you have something like Haskell that is often faster than C in the benchmarks. It's very close to C when it's not faster. It's not like, "Oh, it's twice as slow." No, it's right there, it's within a few percentage of C, and very often it's on the other side, it's faster than C.
https://ericnormand.me/podcast/how-is-haskell-faster-than-c
У кінцевому результаті меседж, який я хочу донести полягає у правильній, гібридизації інструментів для вирішення ваших завдань. Розширюйте ваш світогляд, щоб розглядати проблеми і їхнє вирішення максимально широко.