Что означает программирование на уровне типов во время выполнения?

Я очень новичок в Haskell, поэтому извините, если это основной вопрос или вопрос, основанный на шатком понимании

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

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

Но что означает такая арифметика во время выполнения? Тем более, что в Haskell есть полное стирание типов. Так например

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

Это чисто инструмент безопасности/согласованности во время компиляции? или программирование на уровне типов также дает нам что-то для среды выполнения? Логика и арифметика «оплачиваются во время компиляции» или просто «гарантируются во время компиляции» (если это вообще имеет смысл)?


person World Outsider    schedule 11.05.2018    source источник
comment
Таким образом, на этот был бы простой ответ, за исключением того, что оказалось, что программирование на уровне типов само по себе довольно бесполезно, и поэтому люди очень много работали, чтобы соединить вычисления на уровне типов с вычислениями на уровне терминов. . Почти каждый используемый экземпляр причудливых вычислений на уровне типов имеет параллельное вычисление на уровне терминов, вычисляющее то же самое, но во время выполнения...   -  person Daniel Wagner    schedule 11.05.2018
comment
В GHC есть несколько инструментов, которые помогают поддерживать информацию о типе во время выполнения: Data.Typeable и Data.Data.   -  person MCH    schedule 11.05.2018
comment
Чтобы расширить это, используя что-то вроде Typeable или Proxy, вы можете выполнять операции, используя информацию об этом типе во время выполнения.   -  person MCH    schedule 11.05.2018


Ответы (1)


Как вы правильно сказали, Haskell [без странных расширений] имеет полное стирание типов. Это означает, что все, что вычислено исключительно на уровне типа, стирается во время выполнения.

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

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

Без программирования на уровне типов ваши варианты:

  • Требовать, чтобы вызывающий объект всегда предоставлял векторы одной и той же размерности, и весело возвращать тарабарщину, если это требование не выполняется. (То есть игнорировать проблему.)
  • Выполните явную проверку во время выполнения и создайте исключение или верните Nothing или подобное, если измерение не совпадает.

При программировании на уровне типов вы можете сделать так, что если размеры не совпадают, код не компилируется! Это означает, что во время выполнения вы не Не нужно заботиться о несоответствующем измерении, потому что... ну, если ваш код работает, то размерность не может быть несоответствующей.

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

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


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

Вы также можете сделать более сложные вещи. Где-то я видел библиотеку, которая позволяет определить протокол связи клиент-сервер, но поскольку она кодирует протокол в смехотворно сложные сигнатуры типов [которые автоматически выводит компилятор], она может статически доказать, что клиент и сервер реализуют один и тот же протокол ( т. е. никаких ошибок с сервером, не обрабатывающим одно из сообщений, которые может отправить клиент). Типы стираются во время выполнения, но мы по-прежнему знаем, что проводной протокол не может пойти не так.

person MathematicalOrchid    schedule 11.05.2018
comment
Это в сочетании с комментарием Даниэля Вагнера отвечает на мой вопрос ???? Таким образом, программирование типов - это строго механизм корректности времени компиляции. Это гарантирует, что «нетипизированные двоичные двоичные объекты правильно взаимодействуют во время выполнения», если справедливо описать это так. Он статически определяет, какие функции и данные могут работать вместе. Но вычисление на уровне типа не = вычисление во время выполнения, если термины явно не параллельны типам, и в этом случае не происходит никакого волшебного опережающего вычисления (что, как я знал, не имело смысла, но все же чувствовал интуицию, добирающуюся до в любом случае ). - person World Outsider; 11.05.2018
comment
есть ли шанс, что вы сможете откопать эту библиотеку протоколов клиент/сервер? звучит интересно, хоть и сложно - person World Outsider; 11.05.2018
comment
Я думаю, что нашел это (библиотека протокола клиент/сервер уровня типа) - github.com/haskell-servant/ слуга - person World Outsider; 30.07.2018