LINUX.ORG.RU

Man or boy 2к25: Ваш статически типизированный ЯП полноценен?

 


0

4

Когда то Кнут придумал тест для ALGOL реализаций, и он известен под именем «Man or boy test». Но там просто локальные функции, не особо интересно.

Предоставляю вам версию для проверки языка программирования, на то, достоен ли он существовать в 21 веке!

Для начала нарушу это правило (у Python динамическая типизация), и покажу Python версию:

def print_sum(x):
  def make(acc):
    def f(y):
      print("acc(%d) + %d" % (acc, y))
      return make (acc + y)
    return f
  return make(x)

print_sum(10)(20)(30)(40)
Вывод
acc(10) + 20
acc(30) + 30
acc(60) + 40

Предлагаю попробовать повторить пример на вашем любимом статически типизированном языке, и поделиться им в комментариях.

Мое повторение на OCaml с rectypes:

let print_sum x =
  let rec f acc = fun y ->
    printf "acc(%d) + %d\n" acc y;
    f (acc + y)
  in 
  f x
  
let () = ignore (print_sum 10 20 30 40)
Типы он вывел сам, но можно и указать вручную:
type t = int -> t 

let print_sum (x : int) : t =
  let rec f (acc : int) : t = fun (y : int) : t ->
    printf "acc(%d) + %d\n" acc y;
    f (acc + y)
  in 
  f x
  
let () = ignore (print_sum 10 20 30 40)

Языки которые смогли реализовать тест на лямбдах/функциях, их система типов и ее записи позволяет строить рекурсивные по возврату лямбды и функции:

Языки у которых получилось, но замыкания были выраженны через структуры, потому что они так представленны в самом языке, это облегачает построение рекурсивных по возврату типов, потому что рекурсии в структурах есть везде:

Языки у которых пока не получилось без дополнительных средств типа классов/структур для обхода проблем с типами:

  • Rust (использование trait)
  • C (некорректная реализация)
  • Zig (использование классов)
  • D (использование делегатов)

Не являсь полностью статически типизированным языком, через свою систему типов смогли выразить пример

★★★★★

Последнее исправление: MOPKOBKA (всего исправлений: 21)
Ответ на: комментарий от alysnix

Нет нет нет, никакого одного общего тела. Выдумываю: В процессор игровой приставки добавлены инструкции для разных фигур, методы написанные с использованием этих инструкций выигрываю в разборе фигур на треугольники и последующим сравнением. Так вот, как реализовывать будешь?

MOPKOBKA ★★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

В процессор игровой приставки добавлены инструкции для разных фигур, методы написанные с использованием этих инструкций выигрываю в разборе фигур на треугольники и последующим сравнением

на русский переведи. какие-то «инструкции» для фигур, «выигрываю», разбор на треугольники…

у тебя есть квадрат, треугольник и круг. первые два чистые выпуклые полигоны, последний - выпуклый аппроксимированный. и тебе надо найти их пересечения? там у тебя были какие-то свапы, «хватай мешки - вокзал отходит».

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

alysnix ★★★
()
Ответ на: комментарий от MOPKOBKA

Именование типа делает его неконкретным?

Там же не просто именование. Есть же разница - использовать псевдоним конкретного типа или задавать, как ты называешь, рекурссионный тип функции.

Ладно, мне кажется ты издеваешься.

Да нет. Просто я мыслю более простыми понятиями. А ты оторвался от земли потерял связь между простыми и сложными абстракциями и что-то там шаманишь. Ну может в своём шаманстве видишь что-то, что недоступно мне.

Kogrom
()
Ответ на: комментарий от Kogrom

Не знаю что это за более простые понятия, я всегда остаюсь на уровне ассемблера x86, и мне понятно что никакого dynamic там нет, как и причины по которой компилятор не может разрешить рекурсивный тип с известным размером и характеристиками.

MOPKOBKA ★★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

если вопрос - кто конкретно должен считать стокновения - сами физфигуры или то-то другой. то ответ - кто-то другой. Под названием Collider, «физический мир», «множество обьектов» и так далее. Поскольку отдельной физ. фигуре не нужно знать о существовании других фигур, она есть вещь в себе.

alysnix ★★★
()
Ответ на: комментарий от alysnix

Ладно, ты устроен теперь в Microsoft, и тебе нужно перевести на ООП рельсы старый API.

Есть типы Circle, Triangle, Rectangle. Есть общая функция checkCollision(). Но существуют оптимизированные версии checkCollision_CircleRectangle() и checkCollision_RectangleRectangle() которые пользователю напрямую не должны быть доступны. Как ты опишешь классы, и какие? Нужно просто обернуть эти типы и дать метод checkCollision.

MOPKOBKA ★★★★★
() автор топика
Ответ на: комментарий от alysnix

растишка не дает ничего нового, и на с++ можно писать аналогично грустно и многословно, как и на растишке. потому что растишкины приколы - есть подмножество плюсовых абстракций. просто не наследуй, и дубась все на интерфейсах. медленно и печально.

Ты не знаешь растишку и других не оопешных способов декомпозиции предметки, откуда тебе знать где будет медленно и печально а где нет? Индустрия показала что даже ручной копипаст в какой-нибудь сишке и в меньшей степени гошке, оказывается выгоднее в поддержке чем многоэтажный наследовательный гроб.

zurg
()
Последнее исправление: zurg (всего исправлений: 1)
Ответ на: комментарий от zurg

Ты не знаешь растишку и других не оопешных способов декомпозиции предметки

просто расскажи, какие «новые методы декомпозиции» (неизвестные в с++) дает растишка. То есть, там кто-то придумал новые методы декомпозиции!!!, а мы и не знали?

даже ручной копипаст в какой-нибудь сишке и в меньшей степени гошке

вот. похоже никаких новых методов нет, а есть просто «ручной копипаст», который может быть позитивным только в извращенной логике.

копипаст это создание никак не синхронизируемой КОПИИ. Как только оригинал изменится - копии неизбежно станут невалидными со всеми вытекающими. Вы хотите себе по коду граблей понаставить и ходить по ним с гимнами защищенному программированию.

alysnix ★★★
()
Ответ на: комментарий от MOPKOBKA

Есть типы Circle, Triangle, Rectangle. Есть общая функция checkCollision(). Но существуют оптимизированные версии checkCollision_CircleRectangle() и checkCollision_RectangleRectangle() которые пользователю напрямую не должны быть доступны.

какие там прототипы то? обычная процедурная архитектура легко ложится на ооп, просто потому, что процедурный вариант есть частный случай ооп. На плюсах можно писать чисто процедурно, вообще не привлекая внимания санитаров.

Все зависит от архитектуры твоего процедурного варианта, обычно там все переводится в ооп запросто. ты просто дал названия горсти функции, даже без типов параметров, что я могу по названиям тут сделать? сверху-то там что? кто хранит «физический мир» и как производит обход тел и расчет колиззий. там наверняка какие-то методы отсечения, типа сначала проверяются охыватывающие кубоиды или сферы (это я из 3d) и так далее.

alysnix ★★★
()
Ответ на: комментарий от alysnix

даже без типов параметров, что я могу по названиям тут сделать?

Ну ладно, вот прототипы функций, больше ничего нету!

typedef void *CIRCLE, *TRIANGLE, *RECTANGLE;

CIRCLE CreateGeometryCircle(int radius);
TRIANGLE CreateGeometryTriangle(POINT a, POINT b, POINT c);
REACTANGLE CreateGeometryRectangle(POINT a, POINT b, POINT c, POINT d);

void DestroyGeometry(void *);
bool CheckGeometryCollision(void *a, void *b);

bool CheckGeometryCollisionCircleRectangle(CIRCLE *a, RECTANGLE *b);
bool CheckGeometryCollisionRectangleRectangle(RECTANGLE *a, RECTANGLE *b);

MOPKOBKA ★★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

я всегда остаюсь на уровне ассемблера x86

Ок. Давай тогда посмотрим пример на Окамле из стартового сообщения на годболте, какой там ассемблерный код будет. Там какие-то библиотеки надо добавить. Можешь такое сделать?

Kogrom
()
Ответ на: комментарий от MOPKOBKA

ну вот - три класса уже есть прям в исходнике - круг, треугольник, прямоугольник.

какую-то им надо приделать базу, ориентируясь на общие функции, что на них вызываются(надо смотреть какие это функции). База содержит общие функции по определению и тип_фигуры.

какая-то «CheckGeometryCollision» которую явно надо затащить методом в класс Collider. Ей дают фигуры по базовому классу.

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

Как это написать на с++ - да по разному. По сути тебе надо найти по паре ключей нужную функцию. Тупо можешь построить двумерный массив функций столкновения, и по двум индексам [тип_фигуры, тип _фигуры] тут же находить оптимальную функцию. Быстрей не бывает. Но это не про ООП вообще. это по общей алгоритмистике вопрос или как оно там.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от alysnix

просто расскажи, какие «новые методы декомпозиции» (неизвестные в с++) дает растишка.

Я не говорил «новые» ключевое тут «другие» и разница тут в том что более идиоматично конкретному языку. Трейты, и это нихрена не «просто интерфейсы»(по крайней типичные оопешные), трейты могут работать как интерфейсы но не наоборот; алгебраические типы - в плюсах костылятся через шаблонную жопу; параметрические типы - в плюсах шаблоны, и тут они даже выигрывают по возможностям, но не по синтаксису; функции высших порядков - примерно одна херня, плюс минус…; на самый крайняк макросистема - однозначно выигрывает раст, в плюсах до сих пор сишно-дидовское препроцессорное позорище. В принципе и растовская система владения работает на локализацию рассуждений (что и есть одна из задач декомпозиции) - аналога в плюсах нет, даже с костылянием. И, чуть не забыл, модули, настоящие полноценные - в плюсах какбе появились, но тяжёлоё легаси, обмазанное всё тем же препроцессором, похоже, в плюсах навечно осталось

zurg
()
Последнее исправление: zurg (всего исправлений: 2)
Ответ на: комментарий от zurg

на самый крайняк макросистема - однозначно выигрывает раст

Rust однозначно проигрывает даже С, потому что макросы в С более читаемые, и оправданы появлением в 60х годы. А вот m4 который прикручен сверху в Rust, необъясним вообще никак. Не понимаю как может язык повернуться назвать это достоинством, или вообще называть ЭТО нейтральным словом.

Это буквально макроязык для пытки с применением autotools, только за эти макросы можно невзлюбить Rust.

Можно ли сделать макроязык хуже? Вот у меня идей нету, разве что своровать у Malbolge семантику местами.

В принципе и растовская система владения работает на локализацию рассуждений

Она отягощает программиста рассуждениями про память, и заставляет вместо компилятора прописывать лайфтаймы. У Rust изначально была хорошая идея, сделать нормальный GC, но я так понимаю его не осилили, и решили что неплохо бы программисту ежесекундно думать о том, как компилятору доказать куда потекут байтики, вместо размышлений о задаче.

MOPKOBKA ★★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от Nervous

Как симуляция/модель может быть правильней/лучше реального?

Используется его улучшенная, избавленная от выявленных недостатков версия

Аргументация в стиле «ботва» - просто повторяем одно и тоже по кругу. Я кстати наперёд ответил на это в предыдущем посте. Лишнее подтверждение твоих мотивов/уровня, о чём я тоже сразу написал.

Где выкинуто ненужное наследование реализации, полиморфизм отодран от наследования, а определение данных от поведения.

И опять то же самое.

Мультитул из ножа, молотка, грабель, мясорубки и пылесоса, наверное, можно назвать более широким и мощным решением, чем эти инструменты по отдельности.

Протухшие кричалки мало что значат. А так, любой язык это «мультитул», если ты не в курсе. Ну кроме каких-нибудь дсл, либо ещё чего-то специфичного/немощного. Любая самодостаточная система - это мультитул.

Ну кстати да, раст - не мультитул. Хоть они и пытаются им быть(точнее, создать такое представление о себе), но реальность иная. Поэтому там вместо полноценного компилятора(мультитула) просто парсер поверх ллвм.

Но насчёт полноценности и удобства использования — тут у меня имеются некоторые сомнения.

Ну «у меня»(опять же как я и писал ранее) и «сомнения» это уже ничего не значит. А уж учитывая твой уровень и подавно.

anonymous
()
Ответ на: комментарий от MOPKOBKA

вот примерно как оно должно выглядеть. на первый взгляд

class Figure {
public:  
  enum Kind {f1, f2, f3, max_fig};

protected:  
  Kind _kind{f1};

public:
  explicit Figure (Kind fkind):_kind(fkind) {}
  Kind get_kind() const {return _kind;}
};

//==========================================
///некая фигура
class F1: public Figure {
public:
  F1():Figure(f1) {}
};

///еще фигура
class F2: public Figure {
public:
  F2():Figure(f2) {}
};

//==========================================
class Collider {
  using cFigure = const Figure; ///для краткости
  using CollisionFun = bool (cFigure& ff, cFigure& fff);
 
  ///массив функций столкновений
  CollisionFun* _funcs [Figure::max_fig] [Figure::max_fig] = {};

public:
  Collider() {
	///заполнить массив функций
  }

  ///найти функцию
  CollisionFun* get_collision_fun (cFigure::Kind fk, cFigure::Kind fkk) const {
    return _funcs [fk][fkk];
  }

  ///проверить столкновение
  bool check_collision(cFigure& ff, cFigure& fff) {
    auto lfun = get_collision_fun(ff.get_kind(), fff.get_kind());
    return lfun(ff, fff);
  }
};

void test () {
  F1 lf;
  F2 lff;
  Collider lcl;
  auto lcollision = lcl.check_collision(lf, lff);
}

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

ассемблерный код то вот: https://godbolt.org/z/oWPWEj9dc

Спасибо. Тут что-то чейнинг поломался, но не буду придираться. Ассемблерная простыня знатная получилась, конечно, не то, что в прошлый раз.

Не интересно продолжать эту тему

Ну ок.

Kogrom
()
Ответ на: комментарий от Kogrom

У тебя ссылка неправильная (на пустой godbolt). Чейнинг синтаксический не поломался, можешь вернуть, просто тогда применится внутренний caml_apply и не видно отдельных вызовов.

MOPKOBKA ★★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

Да. Я про этот пример: https://godbolt.org/z/6onMGh39M Исправить на месте не могу, ты слишком быстро ответил.

Чейнинг синтаксический не поломался

Компилятор ругается:

File "<source>", line 10, characters 16-39:
10 | let () = ignore (print_sum 10 20 30 40)
                     ^^^^^^^^^^^^^^^^^^^^^^^
Warning 5 [ignored-partial-application]: this function application is partial,
maybe some arguments are missing.
Compiler returned: 0

Ну ладно, не будем обращать внимание.

Kogrom
()
Ответ на: комментарий от alysnix

Вот тебе два варианта архитектуры: с копипастой (int var)

func1(int var)
{
...
}
func2(int var)
{
...
}

с «наследованием»

int var;
func1(){
  //  используем var
}
func2(){
  //  используем var
}

какой лучше? Базовый класс(в случае наследования реализации) это та же глобалка, только для типов(математики сказали бы изоморфизм), главное, с теми же последствиями. Но стоит обмазать баззвордами про «переиспользование» и «контракты» и оппа, уже методология проектирования. Goto вот тоже не меньше копипасту экономит, но айтишечка почему-то всё-таки ушла от очень компактных, но одноразовых блобов замешанных на goto, указателях и глобалках. Главное правило архитектуры - «разделяй и властвуй», и оно стоит выше DRYя, если последний сильно ломает первое, то нахер такой DRY. С другой стороны, любое обобщение, экономящее копипасту, связывает код, но наследование делает это хуже всех.

zurg
()
Ответ на: комментарий от zurg

наследование дает не это, а вот это

class Base { }

void foo (Base& fobject) {
...
}

class C1: public Base { }
class C2: public С1   { }

foo (C1())
foo (C2())
foo (Base())

то есть НЕ НАРУШАЯ правил СТРОГОЙ типизации foo принимает параметры разных типов, что есть нужный нам полиморфизьм.

Потому что неохота либо приводить типы при использовании foo(что чревато), либо писать несколько реализации foo для разных типов, что будут бинарно одинаковыми.

alysnix ★★★
()
Ответ на: комментарий от MOPKOBKA

А в классическом ООП уже решили давнюю задачу с фигурами?

В классическом ООП 1986-го года делалось так:

(defstruct (triangle (:include figure)) a b c) 
(defstruct (circle (:include figure)) center r)
(defstruct (rect (:include figure)) x y w h)

(defmeth collide ((a figure) (b figure))
  ; общий случай столкновения
  )

(defmeth collide ((a triangle) (b circle))
  (fastCheckCollisionTriangleCirlce a b))

(defmeth collide ((a circle) (b triangle))
  (collide b a))

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от alysnix

то есть НЕ НАРУШАЯ правил СТРОГОЙ типизации foo принимает параметры разных типов, что есть нужный нам полиморфизьм.

Вот именно эта часть в Rust тоже работает. Полезные вещи взяли. Вредные (возможность в Base запихнуть переменную или реализацию метода) выкинули. Выгода.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от zurg

это та же глобалка, только для типов(математики сказали бы изоморфизм), главное, с теми же последствиями

Так в любой крупной программе глобальное состояние есть. От того, что это глобальное состояние почти во всех функциях передаётся первой переменной ничего по сути не меняется. Изоморфизм, да.

Не вижу особого смысла в этом ритуале.

monk ★★★★★
()
Ответ на: комментарий от monk

Вредные (возможность в Base запихнуть переменную или реализацию метода) выкинули. Выгода.

смотря что запихивать. если у всех наследников есть общее состояние и функции - они и будут в базе. Если вы утверждаете, что никаких общих состояний и функций не бывает, то… это как-то даже неприлично для культурного человека.

вы человек культурный? давайте начнем с этого.

alysnix ★★★
()
Ответ на: комментарий от alysnix
enum Figure {F1, F2, F3}

fn check_collision(f1: Figure, f2: Figure) -> bool {
    use Figure::*;
    match (f1, f2) {
        (F1, F2) | (F2, F1) => {
            // specific test
            true
        }
        _ => {
            // generic_collision_test
            false
        }
    }
}

fn test () {
  let fig1 = Figure::F2;
  let fig2 = Figure::F3;
  let lcollision = check_collision(fig1, fig2);
  
  dbg!(lcollision);
}
unC0Rr ★★★★★
()
Последнее исправление: unC0Rr (всего исправлений: 1)
Ответ на: комментарий от alysnix

если у всех наследников есть общее состояние и функции - они и будут в базе.

Вот в этом утверждении и проблема. Когда есть трейт/интерфейс, добавить новый тип, реализующий этот интерфейс не проблема. И все функции, работающие с этим интерфейсом, отлично будут работать с новым типом.

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

То есть указанное утверждение должно действительно быть истинным для всех возможных наследников этого типа, которые кто-либо когда-либо сможет придумать.

monk ★★★★★
()
Ответ на: комментарий от unC0Rr

это что вообще? фиги не могут быть енумами. это должны быть классы с внутренним состоянием(я опустил состояние, но нарисовал сам класс). у вас эти классы не нарисованы.

у вас просто сравниваются енумы, а не конкретные фигуры. как вы будете сравнивать две окружности?

alysnix ★★★
()
Ответ на: комментарий от monk

Вот когда будут, тогда и приходите. Пока что это выглядит как сравнение реализаций 3д движков, где на цпп написали int main() {return 0;} и собираются сравнивать с полным кодом на расте.

unC0Rr ★★★★★
()
Ответ на: комментарий от unC0Rr

ну и досвидос. код на расте вообще ни о чем. там нет ооп, а разговор был о нем. на расте выкатили примитивную ботву в стиле сишечки. зачем раст нужен тогда?

alysnix ★★★
()
Ответ на: комментарий от alysnix

сложно домыслить до ???

Сложно дописать??? Может за тебя вообще написать в цпп всё, что ты хочешь увидеть?

enum Figure {F1{state: u8}, F2, F3}

fn check_collision(f1: &Figure, f2: &Figure) -> bool {
    use Figure::*;
    match (f1, f2) {
        (F1{state}, F2) | (F2, F1{state}) => {
            // specific test
            *state == 0
        }
        _ => {
            // generic_collision_test
            false
        }
    }
}

fn test () {
  let fig1 = Figure::F1{state: 255};
  let fig2 = Figure::F3;
  let lcollision = check_collision(&fig1, &fig2);
  
  dbg!(lcollision);
}
unC0Rr ★★★★★
()
Ответ на: комментарий от monk

потому что было такое требование - что есть специальные функции для пар обьектов.

И чтобы в рантайме выбрать нужную функцию, по-любому надо по паре ключей ее искать. Можно еще этот выбор размазать, по внутренностям обьектов, но при модификации кода это будет требовать больших правок.

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

alysnix ★★★
()