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

В функциональных языках через это можно реализовывать статический va_args, если есть еще аргументы то возвращаем из printf функцию которая принимает аргумент и возвращает продолжение, если нету то void. В С синтаксисе это неудобно бы выглядело:

printf(fmt)(a)(b)(c);
Но в синтаксисе близком к OCaml/Haskell наоборот:
printf fmt a b c 

MOPKOBKA ★★★★★
() автор топика

До сих пор не закончились вопросы зачем это нужно.

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

Версии для ъ не будет, потому что мне от себя нечего добавить. Но могу ответить на любые вопросы.

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

Но в синтаксисе близком к OCaml/Haskell наоборот:

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

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

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

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

Ничего этого ненужно. Ни в каком окампле никакой тег ты не добавишь.

Под тегом я имею виду:

type tag = 
  | EtoInt of int
  | EtoStroka of string
Но ты видимо о чем то другом, у тебя нету примера такого списка на С++? Пусть там два элемента разного типа будут.

Аналогично ненужно путать таплы в цпп и таплы во всяких окамлах.

Если взять примитивные типы, исключив тем самым боксинг, то представлении в памяти будет одно и тоже, (int * int * int) == struct { int, int, int}. Есть ли отличия кроме боксинга вызванного GC?

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

Разница в видимости, я же написал:

// Без rec можно переопределять переменные
let x = 10 in
let x = x + 20 in
print x // 30

// С rec поведение как в С
let x = 10 in
let rec x = x + 20 in // Ошибка, x еще не назначен
print x 

То, что посмотрел какой-то преобразованный из под компилятора

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

У гц-языка все типы ссылочные.

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

Optional не может быть заменой указателя без боксинга. Ты же писал что его нет? Это сложный тип?

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

type 'T option = 
  | None
  | Some of 'T
Тоже боксинг обеспечивает вида struct {int tag; union { void None; GcRef<T> Some; });

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

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

В Lisp это бы выглядело так:

((((printf fmt) a) b) c)

и оказывается никаких «рекурсивных типов» не нужно

Это просто один из примеров.

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

Очень много слов и ничего не складывается в общую картину. Какое отношение (кроме деталей реализации) это имеет к типам, которые могут ссылаться на себя?

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

На уровне языка нету конструкций где понадобилось бы хранить тип

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

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

Как видишь везде возвращается один тип T

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

В цпп всё это работает. В питоне работает. У тебя не работает.

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

((((printf fmt) a) b) c)

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

(printf fmt a b c)

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

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

везде возвращается один тип T

Тип Т - это что? И возвращается иногда T, а иногда T(int).

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

Но мы можем использовать динамику и во время выполнения хитрить. И в этом случае C# честно пишет, что тип - dynamic, а инопланетянские языки это скрывают.

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

Тип Т - это что? И возвращается иногда T, а иногда T(int).

Тип T это «type T = T(int)», если словами, то тип T это функция которая возвращает тип Т и принимает int.

И возвращается иногда T, а иногда T(int).

Это одно и тоже, T определяется как T(int), вот выше T = T(int).

T это функция которая возвращает T и принимает int.

T(int) это функция которая вовзвращает T и принимает int.

Найди отличия.

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

Нет! В структурах же есть рекурсивные типы, вот тут они тоже есть.

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

Так в чём лол то? Чем моё print_sum(10).add(20).add(30) отличается от жабского printSum(10).apply(20).apply(30).apply(40);? или struct S(Box<dyn FnOnce(i32) -> S>); у unC0Rr от дельфового type TAdder = reference to function(i: Integer): TAdder; или хаскелева newtype T = T (Int -> IO T)? которые при этом удостоились верхнего столбца. Логические обоснования какие-нибудь будут, ну, кроме того что у морковки идиосинкразия к слову struct (struct S… это тоже НОВЫЙ тип ) и вообще луна не в той фазе.

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

Лол в том что даже эмулированный пример работает неправильно. Я уже написал в чем разница.

Возможно мне нужно нужно по другому объяснить, но для этого мне нужно лучше понять твое мышление. Объясни чем Rust отличается от brainfuck? На brainfuck я тоже могу написать borrow-checker, трейты там реализовать.

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

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

в компиляторах при парсинге декларации типа это делается просто - декларация типа имеет признак - завершенности. как только парсинг декларации типа заканчивается - этот признак ставится. пока не закончен - стоит false.

если канпилятор видит использование незавершенного типа (кроме ссылки и указателя на него) - он тупо ругается и все.

Если он так устроен - он не распарсит рекурсивный тип у функции. Зато он соблюдает простое кондовое правило - если тип не определен полностью, его нельзя использовать в новых определениях. Дешево, сердито, всем понятно, не вызывает головняка и возможной казуистики. Представим, что тип A определяется через B, тот через С, короче через 150 итераций в конце концов все опять определяется через A, Вот чтобы такой ерунды не было, берем простое правило неиспользования незавершенного типа.

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

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

Всё это имеет смысл исключительно в контексте практической применимости: создания программ, решающих какие-то задачи.

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

Пример понятия, ссылающегося на самого себя: число есть один или число+1. (Но не число есть число+1.)

Пример выражения этого понятия с помощью типов: https://go.dev/play/p/eRUYZ6pyghF

(Кстати, не спешите хвалить Рассела. Его система исключает self-reference из употребления. Он не учитывает разницу, которую я выше подчеркнул в примере с числом. Отсюда его выражение «не будь полностью уверен в чём-либо», обозначающие, что человек не может быть уверен в том, что не может быть уверен в чём-либо.)

Какую более практическую задачу, вроде API, можно решить? Пример. Да, есть несколько способов решить поставленную автором задачу, но ссылающиеся на себя функции обладают приятными особенностями, дословно:

The type assertion in the restoring call to Option is clumsy… . It’s worth noting that since the «verbosity» returned is now a closure, not a verbosity value, the actual previous value is hidden.

Reason enough?

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

если про типы - то построение новых типов на типах, построение которых еще не завершено - должны быть запрещены.

Поздравляю, ты запретил списки в С %)

Представим, что тип A определяется через B, тот через С, короче через 150 итераций в конце концов все опять определяется через A, Вот чтобы такой ерунды не было, берем простое правило неиспользования незавершенного типа.

В OCaml тоже нету такой проблемы, если тип после определения в своей строке остался незавершенным, то это ошибка.

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

Поздравляю, ты запретил списки в С %)

ты просто не умеешь читать.

если канпилятор видит использование незавершенного типа (кроме ссылки и указателя на него) - он тупо ругается и все.
alysnix ★★★
()
Ответ на: комментарий от alysnix

Я думаю единственная причина по которой в С/C++ нету простой возможности построить такой тип, это синтаксис.

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

struct T {
 ... тут можно сослаться на T ...
};

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

Ну так получается будет возможность реализовать мой пример (если будет синтаксис см выше), потому что T в T = T(int) это указатель на функцию.

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

Ну так получается будет возможность реализовать мой пример, потому что T в T = T(int) это указатель на функцию.

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

Потому указатель на незавершенный T сразу вводится в скоп как завершенный. Потому что сам указатель - завершен.

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

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

Ну так получается будет возможность реализовать мой пример, потому что T в T = T(int) это указатель на функцию.

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

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

А этот пример на Golang? Там тоже динамическая типизация?

type T func(int) T

func f(x int) T {
    fmt.Printf("f call with %d\n", x)
    return f
}

func g(x int) T {
    if x > 0 {
        return f
    } else {
        return f(x)
    }
}
Если да, то я не знаю как тебе объяснить, насколько ты глубоко заблуждаешься, уверяя что в статической типизации невозможно выражать рекурсивные типы, я уверен используя классы и структуры ты постоянно ими пользуешься.

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

Это не указатель на тип T, это указатель на функцию, который возвращает указатель где содержится тип T. Что неправильного?

То что он скрыт за уровнями, это ерунда, вот тут тоже тип T скрыт за std::vector, но прежде всего там указатель.

struct T {
  std::vector<T> vec;
};

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

работает неправильно

Делает ровно, тоже самое, ну или все неправильные и ты сам не понимаешь чего хочешь.

На brainfuck я тоже могу написать borrow-checker, трейты там реализовать.

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

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

На brainfuck я тоже могу написать borrow-checker?

Нет

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

Не ожидал такого ответа, не знаю как даже дальше вести диалог.

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

Да, там дальше ты рассказываешь, что даже на плюсах нельзя написать borrow-checker, только святость Rust может помочь справится с такой сложнейшей задачей человечества.

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

Это не указатель на тип T, это указатель на функцию, который возвращает указатель где содержится тип T. Что неправильного?

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

а главное что указатель это новый тип по отношению к базе. Он тривиально завершаем.

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

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

технически тип функция это не адрес, а селектор

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

а тип функция это единое определение, там «указателей» нет

Любая функция в С это указатель, несуществует типа функции без указателя.

Даже разыменование дает указатель:

int main()
{
  *********************main;
}

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

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

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

Любая функция в С это указатель, несуществует типа функции без указателя.

могут быть архитектуры, где у функций нет адресов, але. там «адрес функции» это пара вида (5,25) засунутая в 32 разрядное слово. что означает 5ый модуль, 25ая функция.

зачем так сделано - задачка на сообразительность.

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

А ведь я тебе писал(или не тебе), что 999 из тысячи не знают си. Включая 99% тех кто пишет на си.

Любая функция на си это не указатель. Это тип-функция. Как и тип-массив. Другое дело что они нереалоцируемые поэтому они приводятся автоматом к указателю чтобы выражения с ними имели смысл. Но зачем читать, да?

typedef int puts(const char *);


int main() {
    puts puts;
    puts("hello");
}

https://godbolt.org/z/j85rhTKbh

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

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

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

MOPKOBKA ★★★★★
() автор топика

2к25

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

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

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

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

Так и с функцией, зачем компилятору (если это не замыкание) знать что либо о функции, кроме того что возврат указателя на функцию будет занимать 8 байт или более?

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

Это и будет ее адресом.

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

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

type T = function(int, int):int;

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

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

Динамическая типизация не связана с «узнать». Допустим раст/прочие «языки» не позволяют тебе даже в кт узнать тип. Там нет тайпофа. Но ты называешь это «статическими»? Возможность «пощупать» типы не означает то что они какие-то там это просто отдельная фича.

В этом плане тип динамический. Просто он не сохраняется в рантайме, а далее ты рукой его проводишь. Ты можешь называть это «неизвестными типами», но на самом деле он известный. Я тебе тоже писал об этом. Тип там это *, а не то что ты написал слева. Это как если ты бы тупо кастануло его в инт.

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

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

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

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

святость Rust

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

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

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

Данный спорщик тоже о си ничего не знает, но спорит и воспроизводит какие-то мифы.

имеющими чисто историческую причину. Твои рассуждения имеют чисто одну причину - невежество. А так же безумное повторение такие же невежественных мифов.

нет никаких намеков, что это вообще указатель

На сишке это будет int(int, int). Никаких указателей тут нет.

И да, на него спокойно можно указывать. Будь то через T * ptr как и всё остальное. «несовместимость с указателем» такое же невежество. Скорее всего этот миф обусловлен «что-то слышал про цпп»

Ты, наверное, хотел сказать что он понижает тип. Это свойство реальность. Есть иерархия - тип-функция - функция-указатель - указатель. Всё это есть в си. Допустим поэтому int() в си это вариадик, а не не как в цпп.

Реальность требует такой иерархии. Она такая в си. В си нет прямой редукции к указателю. Не говоря уже об отождествлении.

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

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

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

Вот какую принципиальную разницу вижу я: 1) в моем примере не сохраняется информация о типе нигде, а в других языках в каком то виде это есть, в реализациях trait например 2) преобразование совершается из void (*)(int) и только в void (*)(int), в рантайме это неизменно и постоянно.

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

И да, сохранение типов точно так же является «динамическим» ведь ты называешь донный адт статическим?

Я не говорил что АДТ статический, не совсем понимаю про что ты. Это enum + union, тип variant, dynamic с ограниченным набором значений, который нужен для выбора ветки в рантайме, хороший пример Optional<T> и метод Find в массиве, если элемента нету, то None, если есть то Some T. Статически это не провернуть никак.

Там где нужна статика, ADT не используют.

И вопрос - тег является типом?

Тег для меня это enum привязанный к union. Это деталь внутренней реализации, конкретно в OCaml нельзя к сожалению вынуть из ADT его enum и использовать отдельно. Поэтому типом он точно не является.

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

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

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

Понятно, ты не понял моего вопроса. Так вот, можно ли на brainfuck написать клон rustc, а потом использовать его (это будет компилятор Rust). Если да, то в чем разница между rust и brainfuck?

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

Затем что ты заранее сделал боксинг. И тебе уже это объясняли. Ты пытаешься рассказывать «ну я мыслю через боксинг потому что мой язык иначе не может» - это не значит что это нужно. Что другое ненужно.

Я тебе рассказывал про замыкания. Ты и сам их используешь. Там нет никакого «ну указатель». Есть контекст. Он типа непонятного какого. Смотри.

В сишке есть замыкания. Я могу написать:


typedef struct fn {
    struct fn (*add)(int);
} fn;


main() {
    fn print_sum(x) {
        fn make(acc) {
            fn f(y) {
                printf("acc(%d) + %d\n", acc, y);
                return make(acc + y);
            }
            return (fn){f};
        }
        return make(x);
    }

    print_sum(10).add(20).add(30).add(40);
}

https://gcc.godbolt.org/z/x5PsEdee5

Мне приходится рекурсию свести к агрегату рекурсивному. Это о чём мы говорили, но это отдельная тема. Она ничего не значит.

Но что дальше? Мы не можем с ней ничего делать и поэтому этот код «не работает». В языках с гцц ты спокойно можешь это отдать гц, но здесь нет.

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

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

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

Потом ты можешь использовать это свойство динамического вызова. И там уже нет «зачем мне?». Это ответ на вопрос «если не замыкание». Если на замыкание это всё равно дин-вызов. Дин-вызов уже ограничен.

Допустим в сишке(как и у всего что сделано поверх неё. Считай все языки включая всякие расты) в принципе нет функций как таковых - есть символы. Оно не знает куда и что ты вызывал. Есть проверка сигнатур, но в реальности это просто имена.

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

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

Но это не значит что это одно и тоже либо статических свойств нет.

Вот в дин-языках у тебя функция это какая-то рантайм-сущность. У которой есть указатель и которую нужно хранить/передавать. Потому как ты не знаешь какую функцию вызывать.

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

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

Не могу ничего придумать. Даже если нужны какие то проверки, то они записываются в условный Trait, и на момент проверки совместимости типа с Trait отрабатывают. Ну там к примеру sizeof() типа не должен быть более 100, иначе он не подходит к данному Trait, а значит и к функции принимающей Trait.

Больше всего у меня опыта в С со всякими void* коллекцияами и боксингом. Что дают генерики как в Rust мне понятно к примеру, не надо void* писать. Что дает по сравнению с Rust настоящая статическая типизация?

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

https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Declaring-Funct...

Вот авторитетный источник, указатель на функцию. Размер он имеет фиксированный, указывать может на любую функцию (совместимую по типу, хотя можно и скастовать). Зачем компилятору при объявлении typedef знать что то, кроме того что возвращается указатель на функцию?

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