Поддержка Haskell для отражения типов

Поддерживает ли Haskell отражение типов, как в других языках, таких как Python?

Это отличается от Haskell. Как вывести информацию о какой-либо функции в Haskell, например «ghci> :info func»

Что именно вы спрашиваете? Каков вариант использования для этого? Шаблон Haskell имеет овеществление, если вы используете его только для макросов.

Aplet123 04.04.2023 20:01

Проверка типов во время выполнения поддерживается в таких языках, как Java или C#, но в Haskell в этом нет необходимости, поскольку это язык со строгой типизацией. Если вам нужно поддерживать больше типов для функции, скажем, вы можете использовать полиморфную проверку типов.

Mihai 04.04.2023 20:21

Возможно, вы ищете Typeable, typeOf, eqT и друзей? Или, может быть, просто классы типов? Трудно сказать. Можете ли вы написать какой-нибудь псевдокод, показывающий, как вы будете использовать возможность «программного получения типа» для достижения чего-то?

chi 04.04.2023 20:27

Если вы не запланировали прослушивание типов с помощью Typeable и др., тип не существует во время выполнения. Как только проверка типов завершена, типы стираются, и во время выполнения единственным существующим «типом» являются в основном необработанные биты.

chepner 04.04.2023 21:13
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
145
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Просто для ясности: все специальные команды GHCi, начинающиеся с двоеточия (например, :type, :info и т. д.), не являются частью языка Haskell. GHCi поддерживает их, «оборачивая» фактический Haskell и отслеживая дополнительную информацию. Таким образом, ни одна из этих функций не будет доступна в самом Haskell. Если в Haskell есть что-то, что может решить подобную задачу, нам придется искать это в совершенно другом месте.

Введите стирание

Haskell спроектирован таким образом, что типы полностью стираются во время компиляции.

Скажем, у нас есть значение вроде Just True. При компиляции исходного кода Haskell мы знаем, что он имеет тип Maybe Bool. Но при преобразовании в настоящий машинный код код, который создает это значение, просто выделяет небольшой блок памяти с небольшим числом и указателем на значение True. Число используется, чтобы сказать, был ли конструктор Nothing или Just. Весь код, который обрабатывает значения Maybe, будет скомпилирован таким образом, что он использует небольшое число, чтобы сказать, должен ли он выбрать ветку для конструктора Nothing или конструктора Just; скажем, компилятор выбирает 0 вместо Nothing и 1 вместо Just.

Но в этом маленьком блоке памяти нет ничего, что говорило бы ни о том, что это значение типа данных Maybe, ни о том, что указатель относится к значению типа Bool. Любой другой конструктор с одним аргументом также будет представлен в памяти как просто небольшое число и указатель, а некоторые из них могут даже использовать то же число 1, которое использовалось для Just. Числовые теги нужны только для того, чтобы отличать другие конструкторы от того же типа; нет глобального реестра, присваивающего уникальные номера, чтобы гарантировать, что разные типы не используют одни и те же номера.

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

Отражение

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

Класс Typeable специально поддерживается компилятором. Это класс типов, которые могут быть представлены и проверены во время выполнения. Класс фактически включает все типы; компилятор автоматически создает экземпляры для вас. Наложение ограничения Typeable на вашу функцию означает, что компилятор сделает методы Typeable доступными для вас, сохраняя достаточно информации во время выполнения, чтобы вы могли спросить: «Какой тип этого значения?». Если у вас нет ограничения Typeable для переменной типа, эта информация не будет доступна во время выполнения, и вы не сможете запросить тип. (Это также означает, что ограничения Typeable должны быть добавлены к каждой функции вверх по стеку вызовов до точки, где переменная типа была фактически создана с конкретным типом; если ваши вызывающие объекты не выбрали отражение типа во время выполнения, то вы в одностороннем порядке вернуть нельзя)

То, как вы на самом деле используете его, заключается в том, что вы можете использовать typeOf x для получения значения типа TypeRep a (где a — это тип x). например typeOf True дает вам TypeRep Bool, а typeOf (Just 'a') дает вам TypeRep (Maybe Char) и т. д. Если вам нужно использовать это, вы, вероятно, не знаете, что такое переменная типа a на самом деле, но затем вы можете использовать eqTypeRep, чтобы проверить, равен ли ваш TypeRepTypeRep какого-то другого известного типа, и если это так, вы теперь знаете, что x относится к этому типу и может вызывать для него другие функции, специфичные для этого типа. Вы не можете просто использовать базовые тесты ==, чтобы проверить, равен ли он известному типу, поскольку это дает вам только True или False, которые ничего не доказывают компилятору; это должно выглядеть немного сложнее, но в основном сводится к этой простой идее. Это может выглядеть примерно так:

{-# LANGUAGE GHC2021, GADTs #-}

import Type.Reflection ( Typeable, typeOf, typeRep, eqTypeRep, (:~~:) (HRefl) )

foo :: Typeable a => a -> Integer
foo x = case typeOf x `eqTypeRep` typeRep @Integer of
  Just HRefl -> x + 17
  Nothing -> 0

Внутри Just HRefl плеча case компилятор знает, что x имеет тип Integer, поэтому допустимо добавить 17 и вернуть его как результат функции (которая должна быть чем-то типа Integer). В руке Nothing компилятор не позволит вам использовать функциональность Integer для x или вернуть ее, поэтому вместо этого мы должны вернуть что-то еще, что, как мы знаем, является Integer.

Остальная функциональность в Type.Reflection позволяет вам выполнять более гибкие проверки (например, вы можете проверить, применяется ли тип значения Maybe к чему-либо, не заботясь о том, к какому типу оно применяется). Но в конечном счете все сводится к возможности взять значение, тип которого является переменной типа, и сделать конечное число «догадок» о его типе. Если какая-либо из ваших догадок верна, вы можете действовать в соответствии с этой информацией, но всегда будет вероятность того, что это не какой-либо из типов, которые вы специально проверяете, и вам все равно нужно иметь ветвь, где это просто черный ящик. value (хотя вы всегда можете error отказаться, если вас не волнует, что ваша функция является полной).

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

Полиморфизм

Одна вещь, которая, возможно, не была очевидна из вышеизложенного, заключается в том, что мы не можем отразить полиморфизм. То есть нет способа получить TypeRep, который сам отражает тип, содержащий переменные. Если функция принимает [a], эта переменная типа a будет создаваться при каждом вызове определенного типа, и TypeRep в конечном итоге будет отражать конкретный тип, который использовался в этом конкретном вызове (например, [Integer] или [Maybe Bool] или [Char -> Maybe (IO String)]; он не будет отражать полиморфный тип [a].

Слово предупреждения

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

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

В качестве тривиального примера о функции id :: a -> a часто говорят, что мы можем точно сказать, что она делает, только по ее типу, потому что единственная возможная (полная) реализация функции с типом a -> a — это возврат ее аргумента без изменений. Но если бы его тип был вместо Typeable a => a -> a, мы могли бы очень мало рассказать о том, что он делает. Например, это справедливо:

fakeId :: Typeable a => a -> a
fakeId x = case typeOf x `eqTypeRep` typeRep @Bool of
  Just HRefl -> not x
  Nothing -> x

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

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

Небольшое примечание: typeOf x `eqTypeRep` typeRep @Integer также можно упростить до eqT @a @Integer при условии, что мы добавим явный forall a. в сигнатуру типа. Тем не менее, более короткий вариант делает менее очевидным, что мы используем тип x.

chi 05.04.2023 11:02

Другие вопросы по теме