Понимание оператора Haskell Cast
Предположим, что мы импортировали Data.Typeable
, который содержит cast :: (Typeable a, Typeable b) -> a -> Maybe b
.
Рассмотрим, что
> cast 'n' :: Maybe Char
Just 'n'
пока
> cast 7 :: Maybe Char
Nothing
Я понимаю выше, но это, кажется, тривиальный пример. Он не показывает, почему кому-то нужно будет использовать оператор cast
(насколько я могу видеть).
Вопрос: Есть ли пример использования cast
, который действительно "изменяет" тип значения от одного типа к другому? Самый близкий пример, который я могу придумать (который фактически не работает в GHCi), будет изменять тип 7
от Integer
до Double
:
> cast 7 :: Maybe Double
Just '7' -- this doesn't work, as 7 is typed as a Integer above; instead GHCi returns Nothing
Ответы
Ответ 1
Есть ли пример использования cast, который действительно "изменяет" тип значения от одного типа к другому?
Простой ответ - нет. Но может возникнуть ситуация, когда вы не знаете своего типа, поскольку он "скрыт" от квантификатора "существует". Вот пример:
{-# LANGUAGE ExistentialQuantification #-}
import Data.Typeable
import Data.Maybe
data Foo = forall a . Typeable a => Foo a
main = print . catMaybes . map (\(Foo x) -> cast x :: Maybe Bool) $ xs
where
xs = [Foo True, Foo 'a', Foo "str", Foo False]
Выход будет:
[True,False]
Ответ 2
Цель класса Typeable
- позволить вам работать с данными, когда вы не знаете его точного типа. Целью оператора cast
является проверка того, имеют ли некоторые данные определенный тип, и если да, то вы можете работать с ним как с этим типом. Все дело в том, чтобы делать материал подписи типа ваших данных. Фактическое значение не изменяется вообще.
Если вы хотите изменить значение некоторых данных, а не броска, это преобразование. Для этого есть всевозможные функции. Например, fromIntegral
превращает что-то, что экземпляр Integral
в нечто другое. (Например, от Int
до Double
.) Это не то, что касается кастинга.
Ответ 3
Предположим, вы хотите реализовать функцию, которая действует как функция идентификации для всех значений, за исключением целых чисел, где она должна действовать как функция-преемник.
В нетипизированном языке, таком как Python, это просто:
>>> def f(x):
... if type(x) == int:
... return x+1
... else:
... return x
...
>>> f(42)
43
>>> f("qwerty")
'qwerty'
Даже в Java это выполнимо, даже если для этого требуется кастинг:
static <A> A f(A a) {
if (a instanceof Integer)
return (A) (Integer) ((Integer) a + 1);
else
return a;
}
public static void main (String[] args) throws java.lang.Exception
{
System.out.println(">> " + f(42));
System.out.println(">> " + f("qwerty"));
}
/* Output:
>> 43
>> qwerty
*/
Как насчет Haskell?
В Haskell это невозможно. Любая функция типа forall a. a -> a
должна либо не завершиться, либо вести себя как идентификатор. Это является прямым следствием свободной теоремы, связанного с этим типом.
Однако, если мы можем добавить ограничение Typeable
для типа a
, тогда это становится возможным:
f :: forall a. Typeable a => a -> a
f x = case cast x :: Maybe Int of
Nothing -> x
Just n -> case cast (n+1) :: Maybe a of
Nothing -> x
Just y -> y
Первый cast
преобразует a
в Int
, второй cast
преобразует a
обратно в Int
.
Вышеприведенный код довольно уродлив, так как мы знаем, что второе приведение не может потерпеть неудачу, поэтому нет реальной потребности в втором приведении. Мы можем улучшить код выше с помощью eqT
и равенства GADT типа:
f :: forall a. Typeable a => a -> a
f x = case eqT :: Maybe (a :~: Int) of
Nothing -> x
Just Refl -> x+1
В принципе, eqT
указывает нам, являются ли два (Typeable
) типов равными. Еще лучше, после сопоставления с Just Refl
, компилятор также знает, что они равны, и позволяет нам использовать значения Int
vales вместо значений a
, взаимозаменяемые и без приведения.
Действительно, благодаря GADT, :~:
и eqT
, теперь большинство применений cast
теперь
устарели. cast
может быть тривиально реализовано с помощью eqT
:
cast :: forall a. (Typeable a, Typeable b) => a -> Maybe b
cast x = case eqT :: Maybe (a :~: b) of
Nothing -> Nothing
Just Refl -> Just x
Заключение: в Haskell мы получаем лучшее из обоих миров. У нас есть гарантии параметричности (свободные теоремы) для полиморфных типов. Мы также можем нарушить эти гарантии параметричности и использовать "ad hoc" полиморфизм по цене дополнительного ограничения Typeable
.