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

Это оборачивание в структуру (data), поэтому не могу записать Haskell в какую либо из категорий.

Вот поэтому я использовал newtype, который не является структурой, а только даёт имя типу.

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

Да, в первый список добавляю, прочитал тут что это не ADT и подобное использование подразумевалось: https://www.haskell.org/onlinereport/decls.html#sect4.2.3

Но зачем столько разных слов? data, type, newtype?

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

Избавился от Unsafe. Теперь совсем хорошо.

import Text.Printf

newtype T = T (Int -> IO T)

printSum x = make x where
  make acc = return (T f) where
    f y = do
      printf "acc(%i) + %i\n" acc x :: IO ()
      make (acc + y)

x ~~ y = x >>= (\(T f) -> f y)

main = printSum 10 ~~ 20 ~~ 30 ~~ 40
monk ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Но зачем столько разных слов? data, type, newtype?

Именно для этого. Если нужно просто имя для типизации, но без структуры, тогда newtype. type - это просто псевдоним для сложного типа (как typedef в C).

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

Это когда в установке стоит

main = defaultMainWithHooks gtk2hsUserHooks

А эта штука запускает сборку текста вот таким кодом: https://github.com/gtk2hs/gtk2hs/blob/master/tools/src/Gtk2HsSetup.hs#L442

В общем, понять, что откуда собралось, достаточно проблематично.

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

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

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

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

Я понимаю, когда плагины. Типа pre-inst и post-inst в пакетах.

Я в шоке, когда из этого делают вот такое: https://github.com/haskell-gi/haskell-gi/blob/master/bindings/genBuildInfo.hs#L112

Вот этот файл при установке пакета создаёт новый пакет с именем версией, документацией и своим скриптом по установке пакета. Затем запускает установку сгенерированного пакета.

Вот я сколько пользуюсь Дебианом, ни один разработчик не додумался в предустановочном скрипте через debuild скомпилировать ещё несколько пакетов (со своими предустановочнами скриптами) и установить их.

А тут это норма. Ещё и шаблоны генерируемых исходников прямо в исходниках. Эдакий PHP на стероидах.

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

Ну вот а ТСу не нравится настолько, что он заявил про языки, «достойные существовать в 21 веке».

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

Собственно к языку как знаковой системе это имеет примерно такое же отношение, как споры о скобочках и отступах.

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

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

Ну вот а ТСу не нравится настолько, что он заявил про языки, «достойные существовать в 21 веке».

Это шуточное заявление для подогрева интереса.

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

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

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

Это шуточное заявление для подогрева интереса.

С таким же успехом можно потребовать наличие зависимых типов или сумм типов (если уж хочется показать, что Хаскелл «не дорос»). Или линейных типов (тогда не доросли все, кроме Хаскелла, Идриса и Раста).

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

Обратимся к первоисточнику Самая_сложная_логическая_задача.

Итак, есть правдист T, лжец F и рандом R, которые отвечают в алфавите {J,D}. Что-то из этого правда, а что-то ложь.

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

Предполагаемая таблица истинности

Q | J | T | F 
--+---+---+---
0 | 0 | D | D
0 | 1 | D | D
1 | 0 | J | J
1 | 1 | J | J

Ну с правдистом T всё понятно, раскрутим лжеца F.

К примеру 1 1. Лжет ответил J , а так как он всегда врет, то на Q ответить D, а значит Q - J или в данном случае истинно.

Но ведь идет обращение к лжецу о себе самом, а так как он всегда врет, то он врет и самому себе, а значит есть ещё одна логическая инверсия. Поэтому его ответ должен быть D в этом случае.

Всё это похоже на парадокс Рассела, только никто этого не хочет замечать.

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

Рабочая версия на Idris 2

record T where
  constructor MkT
  runT : Int -> IO T


printSum : Int -> IO T
printSum x = make x
  where
    make : Int -> IO T
    make acc = pure $ MkT $ \y => do
      putStrLn $ "acc(" ++ show acc ++ ") + " ++ show y
      make (acc + y)


private infixl 1 ~~

(~~) : IO T -> Int -> IO T
(~~) x y = do
  MkT f <- x
  f y


main : IO ()
main = ignore $ printSum 10 ~~ 20 ~~ 30 ~~ 40
anonymous
()
Ответ на: комментарий от MOPKOBKA

В расте нет абстрактных классов в плюсовом смысле.

Java пример я засчитал, потому что через используемый там интерфейс, строятся все замыкания,

Засчитываешь ты там что-либо явно по капризу. Не поверишь через что строятся замыкания в расте, посмотри внимательно в учебнике что такое Fn, FnMut, FnOnce

там другая модель замыкания по сравнению с другими языками, тоже самое и в Crystal, но всю выразительную мощь они представляют.

как и в расте

По Haskell моя ошибка, я думал что class связан только с типами,

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

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

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

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

Да что ты несёшь, почитай уже какие-нибудь учебники что-ли

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

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

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

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

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

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

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

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

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

Вот этот файл при установке пакета создаёт новый пакет с именем версией, документацией и своим скриптом по установке пакета. Затем запускает установку сгенерированного пакета.

Я такое видел только вокруг биндингов GTK и Qt. Есть в хаскелловой экосистеме вещи и хуже. Reflex FRP, например.

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

а связь трейта (как набора методов) с данными(то есть записью или как оно там у вас модно называется) делается в расте через жырный поинтер

Покажи мне, где именно поинтер, например, в целочисленных типах. В i32, например.

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

просто пошукай rust trait objects fat pointers. вот то, что в ооп, назвается экземпляром класса, на этом вашем русте называется trait object.

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

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

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

просто пошукай rust trait objects fat pointers. вот то, что в ооп, назвается экземпляром класса, на этом вашем русте называется trait object.

Ты путаешь traits и trait objects. Это разные вещи. В банальном растовом a + b нет никаких жирных указателей, хотя сложение – это тоже трейт.

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

Всё так, но трейты можно использовать статически.

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

Ты путаешь traits и trait objects

аналог трейтов в с++

#define load_save_trait \
void load();\
void save(); 

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

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

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

трейт обьекты - это обьединения vmt(где функции трейта стали вирутальными) с данными для симуляции просто обьектов из с++ или java.

Всё так, но трейты можно использовать статически.

В смысле без vmt? в с++ аналогично. Виртуальная функция будет вызываться без vmt, если при компиляции известно - какую вызывать.

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

Концепты всё же скорее как trait bounds и являются чисто компайл-таймовой штукой, никакого аналога trait object из них не сделать, как и default implementation для методов.

Это да. Жирный минус концептов в том, что они налеплены сверху и плохо интегрированы в остальной C++. Как, впрочем, и вообще всё в С++ :((((

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

уууу… как все запущено.

просто сравни два определения:

https://doc.rust-lang.org/reference/items/traits.html

A trait describes an abstract interface that types can implement. This interface consists of associated items, which come in three varieties:

    functions
    types
    constants

и концепты

https://en.cppreference.com/w/cpp/language/constraints.html

Class templates, function templates (including generic lambdas), and other templated functions (typically members of class templates) might be associated with a constraint , which specifies the requirements on template arguments, which can be used to select the most appropriate function overloads and template specializations.

Named sets of such requirements are called concepts . Each concept is a predicate, evaluated at compile time, and becomes a part of the interface of a template where it is used as a constraint: 

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

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

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

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

Как и трейты. Потому что

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

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

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

Зачем ты так позоришься? Тебе в интернете написали глупость и ты бездумно повторил? Конечно же ты не назвал и не назовёшь ни одного довода в пользу своего наброса.

Трейты не имеют ни одного из механизмов из группы «концептов». Даже минимального аналога не имеют.

Сами же трейты в расте это просто сабсет перегрузки. Если ты пытаешься найти какой-то аналог в цпп. Но оно крайне ограничено и позволяет перегружать только по self и генерик-минорщине. Аналогом это, конечно же, назвать сложно. Несмотря на все их попытки что-то из цпп тащить.

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

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

Каждая из фич концептов является базовым свойством цпп. В том числе и те самые предикаты, которых не существует в трейтах(и ты их никогда не покажешь. Табличный диспатч вида foo/foo не является предикатом). Единственное что сделали концепты - оформили это в виде полноценных языков конструкций.

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

Допустим, чтобы выяснить компилируется код или нет - его подставляли в окружение, которое вытаскивала из сфинае результаты. Это похоже на то как в расте нет даже статик-ассерта и приходится костылять его через обращение к массиву как это делали 30лет назад в си. Теперь ты можешь напрямую спросить «компилируется ли это выражение или нет?»

Концепты имеют совершенно иное назначение в цпп нежели в других языках. То, где они используются в цпп на расте в принципе не выразимо. То, что у тебя называется «предикатами»/«констрейтами» им на самом деле не является и имеем совершенно другую природу.

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

В расте нет абстрактных классов в плюсовом смысле.

dyn Trait является абстрактным классом на минималках.

Не поверишь через что строятся замыкания в расте

Ну так построй через Fn, без отдельного trait с add.

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

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

Иначе тест проходим на brainfuck и становится бессмысленным, нельзя же там запретить какой либо из его операторов, иначе нужно запрещать rec в OCaml. Тут обсуждается не возможность алгоритмически повторить, а возможность выразить через типы.

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

Там vtable и полиморфизм.

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

Нет там никакой рекурсии. Ты просто взял add(add(a, b), c) и зачем записал его как () => add(() => add(() => a, b), c).

Судя по всему ты в принципе не понимаешь что написал, поясню:

trait Then {
    fn add(self, y:i64) -> i64;
}
impl Then for i64 {
    fn add(self, y:i64) -> i64 {
        println!("acc({}) + {}", self, y);
        self + y 
    }
}
fn main() {
    let print_sum = |x| x;
    let acc= print_sum(10).add(20).add(30);
    println!("acc {}", acc.add(100));
}

Остальное шум. Это обычный add без какой-либо рекурсии. Просто нейронка ему это сгенерировала для функции(которая нигде не используется и по которой просто делается перегрузка). Только он этого не понял.

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

Лол. Я даже в код как то особо не вглядывался, вижу trait Then и забраковал. Там еще и println в конце... Оно неправильно работает даже как эмулированная версия.

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

У тебя неверный критерий для сравнения. Ни в каких окамлах нет никаких функий, а в «статически-типизированных языках» нет никаких структур. В прочем жава(и подобным) к ним не относится.

Фокус заключается в том, что шаг рекурсии должен где-то существовать явно. В дин-языках(и окампл это он и есть. Прикрученный стат-анализ не делает его статическим. Как не делает тот же тайпскрипт) он скидывается на рантайм.

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

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

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

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

Ещё пример у тебя дурацкий. Рекурсия на то и рекурсия что должна вызывать самая себя. Но в случае с чейнингом ты всегда можешь его свести к отдельным вызовам(опять же в статическом языке это и будут отдельные вызовы потому как там нет боксинга, но это тупо свойство мироздания). И ты хочешь это проверить, а не не то о чём пишешь.

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

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

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

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

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

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

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

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

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

и по ассемблерному коду видно что никакого dynamic нету

Потому что это ассемблерный код совсем другой программы. В этой новой программе две функции имеют одинаковые сигнатуры.

Ассемблерный код не должен иметь ключевого «dynamic». Динамическая типизация заключается в другом. Если бы у fn_a() и fn_b() были разные типы, то выражение «return n > 0 ? fn_a : fn_b;» и было бы динамической типизацией, потому что мы определили тип во время исполнения, а не во время компиляции.

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

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

Если бы у fn_a() и fn_b() были разные типы, то выражение «return n > 0 ? fn_a : fn_b;» и было бы динамической типизацией, потому что мы определили тип во время исполнения, а не во время компиляции.

Не обязательно. Если тип fn_a равен FA, а тип fn_b — FB, то тип n > 0 ? fn_a : fn_b будет FA U FB. И это определяется при компиляции.

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

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

Так в моем примере на OCaml, типы он сам выводит в функции. Вообще ML/OCaml гарантированно не надо указывать типы, они выводятся сами, невозможна ситуация когда auto надо заменять на что то конкретное.

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

Ассемблерный код не должен иметь ключевого «dynamic».

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

В этой новой программе две функции имеют одинаковые сигнатуры.

В моем примере из ОП-поста тоже одинаковые сигнатуры, возможно ты неправильно понимаешь синтаксис, укажи мне конкретные места, где ты думаешь есть разные типы.

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

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

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

Поэтому никаких «ещё не полностью определённых нет». Просто в случае с дин-языками у тебя там неявный *(условно), а в случае с ц нет. Это в принципе разные понятия.

Фича с неопределённым типов в сишке обусловлена просто тем, что в рамках T * T не является типом. Поэтому если его опустить указатель как был так и остался, но тип разыменования не определён, а значит и само разыменование. Туда даже явно void добавили.

В общем случае в си рекурсия невозможна поэтому её не добавили. В случае с агрегами её добавили просто потому что нужна была для списков и прочего. Это максимально минорная хрень. Люди берут это и начинают путать со всем остальным.

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

Он просто не смог правильно выразить требования. Хотя их в принципе сложно выразить. Допустим 999 из тысячи в принципе не смогут совместить тип в сишке с типом в дин-языке. А практически все «сишники» думают у функции тип void(*)(int), а что такое void(int) они знать не знают.

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

Это как с выводом типов. Массам сложно объяснить почему f(): F<A, B> не является выводом. Ты им показываешь пример они накопипастят таблиц/закат солнца вручную. Они ещё ничего не понимают. Вот ты и берёшь постоянно добавляешь новые условия которые очевидные тебе, но не очевидны им.

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

Допустим, его кон на питоне создают отдельный контекст вызова/захвата/возврата. Пусть это и потому что боксинг/гц, но то неважно в данном случае. И он бы мог сказать «давайте каждая функция будет хранить в список предыдущих значений», но список опять можно свести к боксингу. Ему нужно говорить явно «боксинг запрещён»(условно захват по значению).

Потом ему будут вызывать чейнинг с отдельными вызовами за рекурсию, но это не рекурсия. Вызов должен быть один. Т.е. make должен вызывать сам себя, а не через посредника. Это тоже сложно объяснить.

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

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

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

Ты много написал о рекурсии, но меня интересовала лишь тема рекурсии в типах функций. Для меня никакой магии тут нету, я заранее знал что некоторые языки ее не пройдут, а другие пройдут. Пример намеренно находится на удалении от темы.

Но если говорить о самой рекурсии в движении:

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

А какая реализация в С++? Нужно либо тег добавлять, либо заранее знать как построен список по типам. В OCaml можно и тег добавить (std::variant), и статически такой список выразить (через длинный тип функций которые возвращают std::tuple + хвост из других функций и std::tuple).

Не говоря о том, что он не осилил это без явного указания на rec.

Ты про let rec? Оно только на область видимости влияет, будет ли переменная видна в правой части.

В дин-языках(и окампл это он и есть. Прикрученный стат-анализ не делает его статическим. Как не делает тот же тайпскрипт) он скидывается на рантайм.

В OCaml нету виртуальных функций, типы тоже не сохраняются в runtime, там только боксинг для сложных типов.

Самое простое - в си нельзя выразить рекурсивную структуру.

В OCaml тоже, как и в С нужно добавить «указатель», в случае с OCaml это может быть Optional.

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

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

Никаких FA U FB - это всегда значит боксинг+рантайм-диспатч. Люди сказали что их язык «статически типизированный», потом сказали тоже самое про си. И они давай наделять свои фантазии свойствами си.

С т.е. статического языка любая вариация типов невозможно. Потому что она по определению «не определена». Объединение невозможно в принципе. Или это всегда рантайм-предикат.

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

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

В Scala это сделанно, по моему это вообще убивает всю безопасность типов. Int и String начинают быть общим Object. GADT теряет свой смысл, я как на это наткнулся, отложил ее рассмотрение в сторону.

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

типы он сам выводит в функции.

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

опять же - зачем корячиться с рекурсивным определением типа, если это мало что дает, ну кроме того, чтоб синтакcически приколотьcя и написать fun(a)(b)(c)(d)…

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

В принципе я могу без твоего ответа пометить тебе все типы:

type T = T(int)

// T print_sum(int x)
let print_sum x =
  // T f(int acc)
  let rec f acc = 
    // return T(int y)
    fun y ->
      printf "acc(%d) + %d\n" acc y;
      // return T
      f (acc + y)
  in 
  f x // return T
  
let () = ignore (print_sum 10 20 30 40)
Как видишь везде возвращается один тип T

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

А какая реализация в С++? Нужно либо тег добавлять, либо заранее знать как построен список по типам. В OCaml можно и тег добавить (std::variant), и статически такой список выразить (через длинный тип функций которые возвращают std::tuple + хвост из других функций и std::tuple).

Ничего этого ненужно. Ни в каком окампле никакой тег ты не добавишь. Банально даже потому что ты не смог эту функцию сделать полиморфной по типу/аккумулятору. И вариант это не тег. Это из той же оперы что концепты это какие-то трейты/констрейты из генериков.

Очевидно, что если мы говорим про «статически» мы должны статически эту рекурсию остановить. Как ты хочешь иначе? Аналогично ненужно путать таплы в цпп и таплы во всяких окамлах. Это фундаментально разные понятия.

Ты про let rec? Оно только на область видимости влияет, будет ли переменная видна в правой части.

Это ты так говоришь. Зачем оно нужно если ничего не делает?

В OCaml нету виртуальных функций, типы тоже не сохраняются в runtime, там только боксинг для сложных типов.

Нет. То, что посмотрел какой-то преобразованный из под компилятора это не значит что такова семантика твоего языка. «сохраняются в рантайме» это довод крайне соминетльные и ничего не значащий.

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

В OCaml тоже, как и в С нужно добавить «указатель», в случае с OCaml это может быть Optional.

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

Optional не может быть заменой указателя без боксинга. Ты же писал что его нет? Это сложный тип? Твой Optional должен быть стереджем для объекта, а значит он либо не может быть рекурсивным, либо не является стореджем, либо это боксинг. Не является стореджем это тоже боксинг.

anonymous
()