Небезопасное влечение с ограничениями Haskell

Я играю с пакетом constraints (для GHC Haskell). У меня есть семейство типов для определения того, содержит ли список уровня типа элемент:

type family HasElem (x :: k) (xs :: [k]) where
  HasElem x '[] = False                                                                               
  HasElem x (x ': xs) = True                                                                          
  HasElem x (y ': xs) = HasElem x xs

Это работает, но одна вещь, которая мне не дает, - это знание, что

HasElem x xs   entails   HasElem x (y ': xs)

поскольку семейство типов не является индуктивным определением оператора "является элементом" (как и в agda). Я уверен, что до тех пор, пока GADT не будут продвигаться к типу уровня, нет способа выразить членство в списке с типом данных.

Итак, я использовал пакет constraints, чтобы написать это:

containerEntailsLarger :: Proxy x -> Proxy xs -> Proxy b -> (HasElem x xs ~ True) :- (HasElem x (b ': xs) ~ True)
containerEntailsLarger _ _ _ = unsafeCoerceConstraint

Призрачный, но он работает. Я могу составить совпадение по желанию, чтобы получить то, что мне нужно. Мне интересно, может ли это привести к сбою программы. Кажется, что он не мог, так как unsafeCoerceConstraint определяется как:

unsafeCoerceConstraint = unsafeCoerce refl

И в GHC уровень уровня удаляется во время выполнения. Я думал, что проверю хотя бы, чтобы убедиться, что все в порядке.

--- EDIT ---

Поскольку никто еще не дал объяснений, я подумал, что немного перейду к вопросу. В небезопасном вступлении, которое я создаю, я ожидаю только семейство типов. Если бы я сделал что-то, в котором участвовали словари-словари вместо этого:

badEntailment :: Proxy a -> (Show a) :- (Ord a)
badEntailment _ = unsafeCoerceConstraint

Я предполагаю, что это почти наверняка будет способно вызвать segfault. Это правда? и если да, то чем он отличается от оригинала?

--- ИЗМЕНИТЬ 2 ---

Я просто хотел немного рассказать, почему меня это интересует. Одним из моих интересов является использование удобной кодировки реляционной алгебры в Haskell. Я думаю, что независимо от того, как вы определяете функции для работы над списками на уровне типов, будут очевидные вещи, которые не были бы корректно доказаны. Например, ограничение (для semijoin), которое у меня было раньше, выглядело так (это из памяти, поэтому это может быть не так):

semijoin :: ( GetOverlap as bs ~ Overlap inAs inBoth inBs
            , HasElem x as, HasElem x (inAs ++ inBoth ++ inBs)) => ...

Итак, должно быть очевидно (человеку), что если я возьму объединение двух наборов, он содержит элемент x, который был в as, но я не уверен, что это возможно, чтобы законно убедить решатель ограничений этого. Итак, это моя мотивация для этого. Я создаю вложения, чтобы обмануть решателя ограничений, но я не знаю, действительно ли это безопасно.

Ответы

Ответ 1

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

{-# LANGUAGE ...., UndecidableInstances #-}
type family Or (x :: Bool) (y :: Bool) :: Bool where
  Or 'True x = 'True
  Or x 'True = 'True
  Or x y = 'False

type family Is (x :: k) (y :: k) where
  Is x x = 'True
  Is x y = 'False

type family HasElem (x :: k) (xs :: [k]) :: Bool where
  HasElem x '[] = 'False
  HasElem x (y ': z) = Or (Is x y) (HasElem x z)

containerEntailsLarger :: proxy1 x -> proxy2 xs -> proxy3 b ->
                          (HasElem x xs ~ 'True) :- (HasElem x (b ': xs) ~ 'True)
containerEntailsLarger _p1 _p2 _p3 = Sub Dict

Подход с использованием GADT

У меня возникли проблемы с решением этой проблемы. Здесь можно использовать GADT для получения хороших доказательств при использовании семейств типов и классов для получения хорошего интерфейса.

-- Lots of extensions; I don't think I use ScopedTypeVariables,
-- but I include it as a matter of principle to avoid getting
-- confused.
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, UndecidableInstances #-}
{-# LANGUAGE TypeFamilies, TypeOperators #-}
{-# LANGUAGE DataKinds, PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE GADTs #-}

-- Some natural numbers
data Nat = Z | S Nat deriving (Eq, Ord, Show)

-- Evidence that a type is in a list of types
data ElemG :: k -> [k] -> * where
  Here :: ElemG x (x ': xs)
  There :: ElemG x xs -> ElemG x (y ': xs)
deriving instance Show (ElemG x xs)

-- Take `ElemG` to the class level.
class ElemGC (x :: k) (xs :: [k]) where
  elemG :: proxy1 x -> proxy2 xs -> ElemG x xs

-- There doesn't seem to be a way to instantiate ElemGC
-- directly without overlap, but we can do it via another class.
instance ElemGC' n x xs => ElemGC x xs where
  elemG = elemG'

type family First (x :: k) (xs :: [k]) :: Nat where
  First x (x ': xs) = 'Z
  First x (y ': ys) =  (First x ys)

class First x xs ~ n => ElemGC' (n :: Nat) (x :: k) (xs :: [k]) where
  elemG' :: proxy1 x -> proxy2 xs -> ElemG x xs

instance ElemGC' 'Z x (x ': xs) where
  elemG' _p1 _p2 = Here

instance (ElemGC' n x ys, First x (y ': ys) ~  n) => ElemGC' ( n) x (y ': ys) where
  elemG' p1 _p2 = There (elemG' p1 Proxy)

Это действительно работает, по крайней мере, в простых случаях:

*Hello> elemG (Proxy :: Proxy Int) (Proxy :: Proxy '[Int, Char])
Here

*Hello> elemG (Proxy :: Proxy Int) (Proxy :: Proxy '[Char, Int, Int])
There Here

*Hello> elemG (Proxy :: Proxy Int) (Proxy :: Proxy '[Char, Integer, Int])
There (There Here)

Это не поддерживает точное желание, которое вы желаете, но я считаю, что рекурсивный случай ElemGC', вероятно, является самым близким, с которым вы можете столкнуться с таким информативным ограничением, по крайней мере, в GHC 7.10.