Сравните подходы решения одной и той же задачи — вычисление факториала и перечисление делителей числа — на процедурном (C), объектно-ориентированном (Java с классом), функциональном (Haskell) и логическом (Prolog) языках; приведите примеры кода и проанализируйте преимущества и недостатки каждого подхода с точки зрения выразительности, эффективности и обучения
Ниже показано, как в четырёх парадигмах — процедурной CCC, объектно‑ориентированной JavaJavaJava, функциональной HaskellHaskellHaskell и логической PrologPrologProlog — решаются две простые задачи: вычисление факториала и перечисление делителей числа. Для каждой парадигмы приведён минимальный пример кода и короткий анализ с точки зрения выразительности, эффективности и удобства обучения.
1) C — процедурный подход
Особенности: явный императивный стиль, управление потоком и вычислениями вручную, высокая производительность на низком уровне.
Факториал итер.итер.итер.:
#include <stdio.h> unsigned long long factorialunsignedintnunsigned int nunsignedintn { unsigned long long res = 1; for (unsigned int i = 2; i <= n; ++i) res *= i; return res; } int mainvoidvoidvoid { unsigned int n = 20; printf""%u! = %llu\n", n, factorial(n)"; return 0; }
#include <stdio.h> void divisorsunsignedintnunsigned int nunsignedintn { for (unsigned int d = 1; d <= n; ++d) { if nn % d == 0n printf""%u ", d"; } printf("\n"); } int mainvoidvoidvoid { divisors363636; return 0; }
Оптимизированный вариант делителей доsqrt(n),O(sqrt(n))до sqrt(n), O(sqrt(n))доsqrt(n),O(sqrt(n)):
#include <math.h> /* собрать два делителя при нахождении пары d и n/d */
Анализ CCC
Выразительность: код явный и подробный; для простых задач — понятен, но для абстракций требуется много шаблонного кода.Эффективность: очень высокая низкоуровневыйконтроль,компилятор/оптимизациинизкоуровневый контроль, компилятор/оптимизациинизкоуровневыйконтроль,компилятор/оптимизации. Для факториала нужна большая целочисленная арифметика стандартныйunsignedlonglongпереполнитсястандартный unsigned long long переполнитсястандартныйunsignedlonglongпереполнится.Обучение: просто начать цикл,цикл, %цикл,, но нужно знать детали управления памятью/типов в более сложных задачах.
2) Java — объектно‑ориентированный подход склассомс классомсклассом
Особенности: строгая типизация, классы/методы, стандартные библиотеки BigIntegerдлябольшихфакториаловBigInteger для больших факториаловBigIntegerдлябольшихфакториалов.
Класс с методами:
import java.math.BigInteger; import java.util.ArrayList; import java.util.List; public class NumberUtils { public static BigInteger factorialintnint nintn { BigInteger res = BigInteger.ONE; for (int i = 2; i <= n; i++) res = res.multiplyBigInteger.valueOf(i)BigInteger.valueOf(i)BigInteger.valueOf(i); return res; } public static List<Integer> divisorsintnint nintn { List<Integer> res = new ArrayList<>; for (int d = 1; d * 1L * d <= n; d++) { if nn % d == 0n { res.addddd; int q = n / d; if q!=dq != dq!=d res.addqqq; } } return res; } public static void mainString[]argsString[] argsString[]args { System.out.println"10!="+factorial(10)"10! = " + factorial(10)"10!="+factorial(10); System.out.println"Divisorsof36:"+divisors(36)"Divisors of 36: " + divisors(36)"Divisorsof36:"+divisors(36); } }
Анализ JavaJavaJava
Выразительность: больше «шаблонного» кода, но понятные структуры классы,коллекцииклассы, коллекцииклассы,коллекции. Хорошая библиотечная поддержка BigInteger,коллекцииBigInteger, коллекцииBigInteger,коллекции.Эффективность: обычно чуть хуже C, но JIT-компилятор даёт хорошую производительность; для больших чисел BigInteger дороже, но безопасно.Обучение: полезно усвоить ООП‑концепции; немного больше синтаксической «воды», чем в функциональных языках.
divisors n = [d | d <- [1..n], n `mod` d == 0] -- оптимизированно до sqrt: import Data.List nubnubnub
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 и т.д.).
Ниже показано, как в четырёх парадигмах — процедурной CCC, объектно‑ориентированной JavaJavaJava, функциональной HaskellHaskellHaskell и логической PrologPrologProlog — решаются две простые задачи: вычисление факториала и перечисление делителей числа. Для каждой парадигмы приведён минимальный пример кода и короткий анализ с точки зрения выразительности, эффективности и удобства обучения.
1) C — процедурный подход
Особенности: явный императивный стиль, управление потоком и вычислениями вручную, высокая производительность на низком уровне.Факториал итер.итер.итер.:
#include <stdio.h>unsigned long long factorialunsignedintnunsigned int nunsignedintn {
unsigned long long res = 1;
for (unsigned int i = 2; i <= n; ++i)
res *= i;
return res;
}
int mainvoidvoidvoid {
unsigned int n = 20;
printf""%u! = %llu\n", n, factorial(n)";
return 0;
}
Перечисление делителей простойвариант,O(n)простой вариант, O(n)простойвариант,O(n):
#include <stdio.h>void divisorsunsignedintnunsigned int nunsignedintn {
for (unsigned int d = 1; d <= n; ++d) {
if nn % d == 0n printf""%u ", d";
}
printf("\n");
}
int mainvoidvoidvoid {
divisors363636;
return 0;
}
Оптимизированный вариант делителей доsqrt(n),O(sqrt(n))до sqrt(n), O(sqrt(n))доsqrt(n),O(sqrt(n)):
#include <math.h>/* собрать два делителя при нахождении пары d и n/d */
Анализ CCC
Выразительность: код явный и подробный; для простых задач — понятен, но для абстракций требуется много шаблонного кода.Эффективность: очень высокая низкоуровневыйконтроль,компилятор/оптимизациинизкоуровневый контроль, компилятор/оптимизациинизкоуровневыйконтроль,компилятор/оптимизации. Для факториала нужна большая целочисленная арифметика стандартныйunsignedlonglongпереполнитсястандартный unsigned long long переполнитсястандартныйunsignedlonglongпереполнится.Обучение: просто начать цикл,цикл, %цикл,, но нужно знать детали управления памятью/типов в более сложных задачах.2) Java — объектно‑ориентированный подход склассомс классомсклассом
Особенности: строгая типизация, классы/методы, стандартные библиотеки BigIntegerдлябольшихфакториаловBigInteger для больших факториаловBigIntegerдлябольшихфакториалов.Класс с методами:
import java.math.BigInteger;import java.util.ArrayList;
import java.util.List;
public class NumberUtils {
public static BigInteger factorialintnint nintn {
BigInteger res = BigInteger.ONE;
for (int i = 2; i <= n; i++) res = res.multiplyBigInteger.valueOf(i)BigInteger.valueOf(i)BigInteger.valueOf(i);
return res;
}
public static List<Integer> divisorsintnint nintn {
List<Integer> res = new ArrayList<>;
for (int d = 1; d * 1L * d <= n; d++) {
if nn % d == 0n {
res.addddd;
int q = n / d;
if q!=dq != dq!=d res.addqqq;
}
}
return res;
}
public static void mainString[]argsString[] argsString[]args {
System.out.println"10!="+factorial(10)"10! = " + factorial(10)"10!="+factorial(10);
System.out.println"Divisorsof36:"+divisors(36)"Divisors of 36: " + divisors(36)"Divisorsof36:"+divisors(36);
}
}
Анализ JavaJavaJava
Выразительность: больше «шаблонного» кода, но понятные структуры классы,коллекцииклассы, коллекцииклассы,коллекции. Хорошая библиотечная поддержка BigInteger,коллекцииBigInteger, коллекцииBigInteger,коллекции.Эффективность: обычно чуть хуже C, но JIT-компилятор даёт хорошую производительность; для больших чисел BigInteger дороже, но безопасно.Обучение: полезно усвоить ООП‑концепции; немного больше синтаксической «воды», чем в функциональных языках.3) Haskell — функциональный подход
Особенности: чистая функциональность, ленивость, мощные абстракции, встроенная произвольная целая арифметика.Факториал короткокороткокоротко:
-- рекурсивноfactorial 0 = 1
factorial n = n * factorial n−1n-1n−1
-- или стандартный стиль
factorial' n = product 1..n1..n1..n
Перечисление делителей простойвариантпростой вариантпростойвариант:
divisors n = [d | d <- [1..n], n `mod` d == 0]-- оптимизированно до sqrt:
import Data.List nubnubnub 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 и т.д.).