Поиск…


Вступление

TypeApplications являются альтернативой аннотациям типа, когда компилятор пытается вывести типы для данного выражения.

Эта серия примеров объяснит цель расширения TypeApplications и способы его использования

Не забудьте включить расширение, разместив {-# LANGUAGE TypeApplications #-} в верхней части исходного файла.

Избегание аннотаций типа

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

x :: Num a => a
x = 5

main :: IO ()
main = print x

Этот код имеет ошибку неоднозначности. Мы знаем, что a имеет экземпляр Num , и для его печати мы знаем, что ему нужен экземпляр Show . Это могло бы работать, если a был, например, Int , поэтому, чтобы исправить ошибку, мы можем добавить аннотацию типа

main = print (x :: Int)

Другое решение, использующее приложения типа, будет выглядеть так:

main = print @Int x

Чтобы понять, что это значит, нам нужно посмотреть на подпись типа print .

print :: Show a => a -> IO ()

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

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

Итак, чтобы разбить приведенный выше пример

print :: Show a => a -> IO ()
print @Int :: Int -> IO ()
print @Int x :: IO ()

Тип приложений на других языках

Если вы знакомы с такими языками, как Java, C # или C ++ и концепцией generics / templates, то это сравнение может быть полезно для вас.

Скажем, у нас есть общая функция в C #

public static T DoNothing<T>(T in) { return in; }

Чтобы вызвать эту функцию с помощью float мы можем сделать DoNothing(5.0f) или если мы хотим быть явным, мы можем сказать DoNothing<float>(5.0f) . Эта часть внутри угловых скобок является типом приложения.

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

doNothing :: a -> a
doNothing x = x

Это также можно сделать явным, используя ScopedTypeVariables , Rank2Types или RankNTypes подобные этому.

doNothing :: forall a. a -> a
doNothing x = x

Затем на сайте вызова мы снова можем либо написать doNothing 5.0 либо doNothing @Float 5.0

Порядок параметров

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

const :: a -> b -> a

Имеет ли запись const @Int значение a равно Int , или это b ? Если мы явно укажем параметры типа, используя forall например const :: forall a b. a -> b -> a тогда порядок написан так: a , тогда b .

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

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

const @_ @Int

Тип этого выражения

const @_ @Int :: a -> Int -> a

Взаимодействие с неоднозначными типами

Предположим, вы вводите класс типов, размер которых в байтах.

class SizeOf a where
    sizeOf :: a -> Int

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

Без приложений типа наилучшим решением было Proxy тип, определенный таким образом

data Proxy a = Proxy

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

class SizeOf a where
    sizeOf :: Proxy a -> Int

Теперь вам может быть интересно, почему бы вообще не отказаться от первого аргумента? Тогда тип нашей функции будет просто sizeOf :: Int или, если быть более точным, потому что это метод класса sizeOf :: SizeOf a => Int или быть еще более явным sizeOf :: forall a. SizeOf a => Int .

Проблема заключается в выводе типа. Если я буду писать sizeOf где-то, алгоритм вывода только знает, что я ожидаю Int . Он не знает, какой тип я хочу заменить a . Из-за этого определение будет отклонено компилятором, если вы не включили расширение {-# LANGUAGE AllowAmbiguousTypes #-} . В этом случае определение компилируется, его просто невозможно использовать нигде без ошибки двусмысленности.

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



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow