Понимание оператора 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.