Ответ на вопрос
Ниже показано, как в четырёх парадигмах — процедурной (C), объектно‑ориентированной (Java), функциональной (Haskell) и логической (Prolog) — решаются две простые задачи: вычисление факториала и перечисление делителей числа. Для каждой парадигмы приведён минимальный пример кода и короткий анализ с точки зрения выразительности, эффективности и удобства обучения.1) C — процедурный подходОсобенности: явный императивный стиль, управление потоком и вычислениями вручную, высокая производительность на низком уровне.Факториал (итер.):#include <stdio.h>
unsigned long long factorial(unsigned int n) {
unsigned long long res = 1;
for (unsigned int i = 2; i <= n; ++i)
res *= i;
return res;
}
int main(void) {
unsigned int n = 20;
printf("%u! = %llu\n", n, factorial(n));
return 0;
}Перечисление делителей (простой вариант, O(n)):#include <stdio.h>
void divisors(unsigned int n) {
for (unsigned int d = 1; d <= n; ++d) {
if (n % d == 0) printf("%u ", d);
}
printf("\n");
}
int main(void) {
divisors(36);
return 0;
}Оптимизированный вариант делителей (до sqrt(n), O(sqrt(n))):#include <math.h>
/* собрать два делителя при нахождении пары d и n/d */Анализ (C)Выразительность: код явный и подробный; для простых задач — понятен, но для абстракций требуется много шаблонного кода.Эффективность: очень высокая (низкоуровневый контроль, компилятор/оптимизации). Для факториала нужна большая целочисленная арифметика (стандартный unsigned long long переполнится).Обучение: просто начать (цикл, %), но нужно знать детали управления памятью/типов в более сложных задачах.2) Java — объектно‑ориентированный подход (с классом)Особенности: строгая типизация, классы/методы, стандартные библиотеки (BigInteger для больших факториалов).Класс с методами:import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
public class NumberUtils {
public static BigInteger factorial(int n) {
BigInteger res = BigInteger.ONE;
for (int i = 2; i <= n; i++) res = res.multiply(BigInteger.valueOf(i));
return res;
}
public static List<Integer> divisors(int n) {
List<Integer> res = new ArrayList<>();
for (int d = 1; d * 1L * d <= n; d++) {
if (n % d == 0) {
res.add(d);
int q = n / d;
if (q != d) res.add(q);
}
}
return res;
}
public static void main(String[] args) {
System.out.println("10! = " + factorial(10));
System.out.println("Divisors of 36: " + divisors(36));
}
}Анализ (Java)Выразительность: больше «шаблонного» кода, но понятные структуры (классы, коллекции). Хорошая библиотечная поддержка (BigInteger, коллекции).Эффективность: обычно чуть хуже C, но JIT-компилятор даёт хорошую производительность; для больших чисел BigInteger дороже, но безопасно.Обучение: полезно усвоить ООП‑концепции; немного больше синтаксической «воды», чем в функциональных языках.3) Haskell — функциональный подходОсобенности: чистая функциональность, ленивость, мощные абстракции, встроенная произвольная целая арифметика.Факториал (коротко):-- рекурсивно
factorial 0 = 1
factorial n = n * factorial (n-1)
-- или стандартный стиль
factorial' n = product [1..n]Перечисление делителей (простой вариант):divisors n = [d | d <- [1..n], n `mod` d == 0]
-- оптимизированно до sqrt:
import Data.List (nub)
divisors' n = nub $ concat [[d, q] | d <- [1..isqrt n], let (q,r) = n `divMod` d, r==0]
where isqrt = floor . sqrt . fromIntegralАнализ (Haskell)Выразительность: очень высокая — компактные и декларативные выражения; code-as-math.Эффективность: для простых списковых построений часто конкурентоспособно; ленивость и оптимизации могут помочь, но иногда требуют внимания (избегать ненужной рекурсии, использовать строгие структуры).Обучение: крутая кривая вхождения (функциональное мышление, монады и т.д.), но простые программы пишутся очень кратко и ясно.4) Prolog — логический (декларативный) подходОсобенности: декларация фактов и правил, поиск/бэктрекинг, неявное перечисление ответов.Факториал (рекурсивно):factorial(0, 1).
factorial(N, F) :-
N > 0,
N1 is N - 1,
factorial(N1, F1),
F is N * F1.Запрос: ?- factorial(5, F). даст F = 120.Перечисление делителей (повторные решения через backtracking):divisor(N, D) :- between(1, N, D), 0 is N mod D.Собрать все делители в список:divisors(N, Ds) :- findall(D, divisor(N, D), Ds).Анализ (Prolog)Выразительность: очень удобно описывать отношения и правила; генерация решений (делители) предоставляется «бесплатно» через backtracking.Эффективность: система поиска/контроля делает Prolog хорошим в задачах по логическому выводу, но арифметика и числа часто менее эффективны, чем в C/Java/Haskell.Обучение: требует изменения мышления (декларативный подход, unification, backtracking); для задач, где естественно формулировать правила, очень удобно.Сводный сравнительный анализ по критериямВыразительностьHaskell: очень выразителен для чистых вычислений (короткие, читаемые выражения).Prolog: выразителен для задач, где нужно описать отношения/правила и получать множество решений.Java: более многословен, но хорош для организации кода и повторного использования (API, OOP).C: самый явный; требует больше кода для абстракций.Эффективность (в типичных реализациях)C: чаще всего самый быстрый для числовых операций и низкоуровневых задач.Java: близка к C в некоторых сценариях благодаря JIT, хороша для реальных приложений.Haskell: может быть очень эффективен при оптимизациях; для простых алгоритмов конкурентоспособен, но ленивость и абстракции могут вносить накладные расходы.Prolog: хорош для символической обработки и поиска, но менее эффективен в чисто численных интенсивных вычислениях.Удобство обучения и модель мышленияC: простые концепции — легко начать, но для крупных систем нужно много дисциплины.Java: требует понимания ООП; много «шаблонного» кода, но широко используем в индустрии.Haskell: крутая кривая вхождения, но даёт мощные абстракции и чистоту в результате.Prolog: непривычен для тех, кто привык к императиву; силён в задачах вывода, но не универсален для всех задач.Дополнительные замечания и практические аспектыБольшие факториалы: стандартные целочисленные типы в C/Java переполняются; Haskell имеет Integer с произвольной точностью, Java — BigInteger, Prolog в большинстве реализаций тоже поддерживает большие целые (но производительность различается).Рекурсия и стек: рекурсивные версии факториала в C и Java могут переполнить стек при большом n; Haskell поддерживает оптимизации (tail recursion/строгость), Prolog — зависит от реализации.Оптимизация делителей: все языки легко поддерживают улучшение до проверки до sqrt(n). В Haskell и Prolog декларативные подходы позволяют компактно выразить получение обеих сторон пары делителей.Выбор парадигмы зависит от задачи: численные вычисления и системное программирование — C/Java; сложные математические выражения и сопоставление с образцом — Haskell; задачи логического вывода и перебора — Prolog.Короткие рекомендацииДля максимальной скорости и контроля — C.Для безопасных больших целочисленных вычислений и объектной архитектуры — Java.Для краткости, высокой абстракции и чистой функциональной модели — Haskell.Для задач спецификации правил/поиска решений — Prolog.Если хотите, могу:привести оптимизированные реализации (например, выбор делителей через sqrt с аккуратным упорядочиванием вывода) для каждого языка;показать сравнение по времени выполнения на конкретном n;или объяснить, как обрабатывать очень большие факториалы (использование библиотек GMP в C, BigInteger в Java и т.д.).
Еще