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)
Ответ на: комментарий от unC0Rr

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

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

ладно счас выкачу более лучший код, чтобы растаманы не тормозили.

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

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

И это повод поломать саму идею ООП, что расширение производится созданием наследников, а не редактирование базового класса?

Если всё равно редактировать Figure, так можно прямо в нём делать collide(Figure second) и в нём просто перебирать через case типы фигур.

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

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

Экспромптом.
Добавили бы в архитектуру команд команду типа: «область» за которой следовала бы INT, содержащая длину области и команды для работы с областью.
Вот вам и аппаратный Rust.

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

Добавили бы в архитектуру команд команду типа: «область» за которой следовала бы INT, содержащая длину области и команды для работы с областью.

В Эльбрусе есть.

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

нате

struct Coord {
	int x,y;
};

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

protected:  
  Kind _kind{f1};
  Coord _pos;

public:
  Figure (Kind fkind, const Coord& fp):_kind(fkind), _pos(fp) {}
  Kind get_kind() const {return _kind;}
  const Coord& get_pos() const {return _pos;}
};


//==========================================
///некая фигура
class Circle: public Figure {
  int _r;
public:
  Circle(const Coord& fp, int fr)
	:Figure(f1,fp), _r(fr) 
	{}
};

///еще фигура
class Rect: public Figure {
  uint _w,_h;
public:
  Rect(const Coord& fp, uint fw, uint fh)
	:Figure(f1, fp), _w(fw), _h(fh)
	{}
};

//==========================================
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 () {
  Collider lcl;
  
  auto la =  lcl.check_collision (
     Circle({0,0}, 100),
     Circle({10,20}, 150)
  );
  
  auto lb = lcl.check_collision (
     Rect({0,0}, 100, 200),
     Circle({10,20}, 150)
  );

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

И это повод поломать саму идею ООП, что расширение производится созданием наследников, а не редактирование базового класса?

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

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

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

для матрицы специфических функций сравнения которые надо применить в коде по условию задачи.

CollisionFun* _funcs [Figure::max_fig] [Figure::max_fig] = {};
alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от monk

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

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

У меня по другому сделано.
Создаём набор метаданных, содержащий архитектуру объекта, а core
используя метаданные может работать с объеками любой сложности и иерархии.
О метаданных компилятор ничего не знает …
Проще говоря core «не прибито гвоздями» к объектам.
Ныне принцип разработки vs.
Поэтому-то у фирмы 1С 60000 классов, реализующии функциональность платформы, а у меня ни одного.

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

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix
struct Coord {
    x: i32,
    y: i32,
}

enum Figure {
    Circle{coord: Coord, radius: u32},
    Rectangle{coord: Coord, width: i32, height: i32},
}

use Figure::*;

fn check_circles(f1: &Figure, f2: &Figure) -> bool {
    //todo!()
    true
}

fn check_collision(f1: &Figure, f2: &Figure) -> bool {
    match (f1, f2) {
        (Circle{..}, Circle{..}) => {
            check_circles(f1, f2)
        }
        _ => {
            // hypothetical_generic_collision_test
            false
        }
    }
}

fn test() {
  let la = check_collision(
     &Circle{coord: Coord{x: 0, y: 0}, radius: 100},
     &Circle{coord: Coord{x: 10, y: 20}, radius: 150}
  );
  
  let lb = check_collision(
     &Rectangle{coord: Coord{x: 10, y: 20}, width: 100, height: 200},
     &Circle{coord: Coord{x: 10, y: 20}, radius: 150}
  ); 
  
  dbg!(la);
  dbg!(lb);
}
unC0Rr ★★★★★
()
Ответ на: комментарий от anonymous

То бишь к примеру как реализованы функции для работы с строками такие же как в crt фирмы 1С.
Да просто.
У меня своё API для работы с строками.
Три строки и какая-либо функция 1C для работы с строками готова …

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

Есть

monk@alatyr:~$ cat /proc/cpuinfo
processor       : 0
vendor_id       : MBE1C-PC
cpu family      : 4
model           : 8
model name      : E1C+
revision        : 2
cpu MHz         : 984
bogomips        : 1968.00

cache0          : level=1 type=Instruction scope=Private size=128K line_size=256 associativity=4
cache1          : level=1 type=Data scope=Private size=64K line_size=32 associativity=4
cache2          : level=2 type=Unified scope=Private size=2048K line_size=64 associativity=4
monk@alatyr:~$ uptime
 17:49:33 up 76 days, 21:08,  2 users,  load average: 0,06, 0,02, 0,00

И в апреле мне звонили, что производство возобновили.

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

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

То есть наличие в базовом классе enum Kind с полным списком наследников подразумевает, что от него нельзя наследовать новые классы?

monk ★★★★★
()
Ответ на: комментарий от unC0Rr
enum Figure {
    Circle{coord: Coord, radius: u32},
    Rectangle{coord: Coord, width: i32, height: i32},
}

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

check_circles нам не надоть, их нет в исходном коде.

match (f1, f2) {

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

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

То есть наличие в базовом классе enum Kind с полным списком наследников подразумевает, что от него нельзя наследовать новые классы?

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

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

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

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

Нет. Условный переход дешевле, чем вызов функции по адресу в регистре. Для трёх видов фигур таблица точно дороже.

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

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

Под эту задачу и коллайдер не нужен. Просто жёстко забиваешь фиксированные фигуры в базовый класс и всё. Как сделано в варианте на Расте.

По нормальной схеме, раз уж сравнивать ООП с Растом, надо требовать возможности расширения без изменения базового кода.

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

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

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

Александреску на вас нет…

Регистрировать надо виды фигур в коллайдере и методы функции столкновений для новых видов тоже.

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

Нет. Условный переход дешевле, чем вызов функции по адресу в регистре. Для трёх видов фигур таблица точно дороже.

не вы тут заказываете музыку, а морковкин. и не надо нас учить ассемблеру, мы и так ученые всяко.

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

либо пусть делает 9 вариантов в матче своем, либо таблицу. и посмотрим кто там быстрей.

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

Под эту задачу и коллайдер не нужен.

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

вы этим никогда не занимались небось?

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

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

Вот для 9 ещё таблица переходов типа

switch(a) {
  case 1:
    switch(b) {
    }
  ...
}

дешевле. Матч делает для малого количества веток переходы, для большого сам переходы собирает в таблицу.

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

Что делать, когда в двух модулях фигуры, которые не знают друг о друге, как их коллидировать?

Какой вид фигуры первый, тот и будет определять «столкновение заданного вида фигуры с неизвестным объектом».

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

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

Может. Как и любая операция. Но не имеет. Поэтому не нужен.

Иначе из этих же соображений нам нужны: сумматор, умножитель, управляющий файлами, управляющий доступом к полям структуры, управляющий выполнением методами объекта…

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

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

а потом весь этот бандл надо отдать коллайдеру - он сам разберется.

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

дешевле. Матч делает для малого количества веток переходы, для большого сам переходы собирает в таблицу.

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

lfun = *(&table + y * max_tag + x) 

безо всяких джампов вообще. ну просто нереально сделать быстрей. а если max_tag известен заранее, там вообще одна асмовая команда получается. за счет моды адресации base[reg1*const + reg2].

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

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

И что с этим адресом делать? Дальше call rax, который гарантированно ломает конвейер, так как до вычисления никуда перейти нельзя.

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

если max_tag известен заранее, там вообще одна асмовая команда получается. за счет моды адресации base[reg1*const + reg2].

напиши маш код для

call rdx[rax*4 + rbx]
monk ★★★★★
()
Ответ на: комментарий от alysnix

Вызов по фиксированному адресу дешёвый. Переход по фиксированному адресу дешёвый. Условный переход дорогой только если веток больше, чем может заранее рассчитать процессор. Переход (хоть на функцию хоть просто) на адрес в регистре очень дорогой. Смягчается, если адрес совпал с предыдущим переходом за счёт предсказателя переходов.

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

Вызов по фиксированному адресу дешёвый.

можете написать тестик - что быстрей вызов фиксированной функции после двух джампов по условию. или вызов функции из таблицы 3*3.

поскольку call это тупо джамп, только с сохранением ip на стек. а первом случае мы имеем три джампа и одно сохранение на стек, во втором, один джамп и одно сохранение.

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

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

Классы C++ вполне можно использовать.
Криминала в этом никакого нет.
Мне всё же больше интерфейсы нравятся.
class не использую лишь потому, что своя объектная подсистема разработана.
class использую лишь для COM и ActiveX.

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

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

Профессионалу нельзя, а «молодым» всё можно.

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

можете написать тестик - что быстрей вызов фиксированной функции после двух джампов по условию. или вызов функции из таблицы 3*3.

5 лет назад до трёх джампов было быстрее вызова. Размер таблицы роли не играл. Начиная с четырёх было быстрее по таблице.

Сейчас так: https://godbolt.org/z/66bqao3MK

Если в switch четыре ветки, в ассемблере условия. Если 5 и больше, то переход по таблице.

Вот это https://godbolt.org/z/Eacv5v6jx в таблицу не превращает.

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

поскольку call это тупо джамп, только с сохранением ip на стек. а первом случае мы имеем три джампа и одно сохранение на стек, во втором, один джамп и одно сохранение.

Для процессора проблема именно в этом сохранении. Когда он натыкается на jmp 42, он сразу же может читать с адреса 42. Когда он натыкается на jmp rax, то приходится останавливаться и ждать, пока вычисление rax не закончится.

мы имеем три джампа и одно сохранение на стек

Почему сохранение? Все адреса прописаны явно.

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

Когда он натыкается на jmp 42, он сразу же может читать с адреса 42. Когда он натыкается на jmp rax, то приходится останавливаться и ждать, пока вычисление rax не закончится.

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

Почему сохранение? Все адреса прописаны явно.

адрес возврата ему ж надо на стек записать.

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

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

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

чтобы на такое наткнуться ему надо сделать минимум два джампа, выбора ветки if, или что там у вас

Поэтому я и пишу, что вопрос в длине конвейера и буфера декодирования.

Процессор читает вперёд, натыкается на je 42. Теперь он читает две ветки, следующую после je и после адреса 42. В каждой из них опять натыкается на условные переходы, читает четыре ветки. И так пока буфер не кончится. Когда вычисление пойдёт по одной из веток, лишние данные с буфера уйдут. Нынешние процессоры имеют буфер декодирования на 168 операций. Конвейер на 32 операции.

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

https://godbolt.org/z/Pa5jvPnGq

вот я дописал выбор из таблицы. сравните свою простыню с таким асмом, торг здесь неуместен!(с)

_Z4testii:
 movsxd rdi,edi
 movsxd rsi,esi
 lea    rax,[rdi+rdi*2]
 add    rax,rsi
 jmp    QWORD PTR [rax*8+0x0]
    R_X86_64_32S _table
alysnix ★★★
()