Ограничение экземпляров

Допустим, у нас есть следующее:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilyDependencies #-}

type family CategoryLikeT p_a_b = t | t -> p_a_b

type IsCategoryLike p t a b = (t ~ CategoryLikeT (p, a, b))

class CategoryLike p where
  (>>>) :: 
    (
      IsCategoryLike p t1 a b, 
      IsCategoryLike p t2 b c, 
      IsCategoryLike p t3 a c
    ) => t1 -> t2 -> t3

Затем мы обнаруживаем, что это компилируется нормально:

f :: 
  (
    CategoryLike p, 
    IsCategoryLike p t1 a b, 
    IsCategoryLike p t2 b c, 
    IsCategoryLike p t3 c d, 
    IsCategoryLike p t4 a d
  ) => t1 -> t2 -> t3 -> t4
f x y z = x >>> y >>> z

Но мы еще не определили ни одного экземпляра. Давайте сделаем это:

data BasicFunction
type instance CategoryLikeT (BasicFunction, a, b) = a -> b

instance CategoryLike BasicFunction where
  (>>>) = flip (.)

Но также «Ints» при добавлении являются своего рода категорией, если мы просто предположим, что «a» и «b» оба являются Void, например: экземпляр типа данных BasicInt CategoryLikeT (BasicInt, Void, Void) = Int

instance CategoryLike BasicFunction where
  (>>>) = (+)

Конечно, приведенное выше не работает, потому что в определении экземпляра нет ограничений на «a» или «b», поэтому нет гарантии, что >>> получит все одинаковые типы, поэтому (+) не является достаточно общим. Итак, я подумал, что делаю следующее:

Во-первых, добавление типа ограничения:

type family CategoryConstraints p t a b

А затем добавить к определению IsCategoryLike следующее:

type IsCategoryLike p t a b = 
  (t ~ CategoryLikeT (p, a, b), CategoryConstraints p t)

Затем мы можем добавить следующее ограничение:

type instance CategoryConstraints BasicInt t = (t ~ Int)

Но теперь у нас есть проблема. f больше не работает, выдавая эту ошибку:

Could not deduce: CategoryConstraints p (CategoryLikeT (p, a, c)))

Мы можем исправить это двумя способами:

Во-первых, добавив IsCategoryLike p t5 a c к ограничениям в f. Но это может быстро запутать более сложные функции, вам придется добавлять ограничение для каждой операции. Также тривиальные изменения, такие как изменение (x >>> y) >>> z на x >>> (y >>> z), требуют изменения подписи, что не требовалось, когда не было ограничений.

В качестве альтернативы сигнатура типа может быть полностью опущена или могут использоваться сигнатуры частичного типа.

Тем не менее, я хотел бы сохранить полные подписи типов, не увеличивая их и не усложняя обслуживание. Могут ли люди предложить альтернативные подходы?


person Clinton    schedule 16.08.2016    source источник


Ответы (1)


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

{-# LANGUAGE TypeFamilies, 
             ConstraintKinds,
             FlexibleInstances,
             TypeFamilyDependencies #-}

import GHC.Exts (Constraint)

class CategoryLike p where
  type CategoryLikeT p a b = t | t -> p a b
  type CategoryConstraints p a b :: Constraint
  type CategoryConstraints p a b = ()
  (>>>) :: (CategoryConstraints p a b, CategoryConstraints p b c, CategoryConstraints p a c) 
    => CategoryLikeT p a b -> CategoryLikeT p b c -> CategoryLikeT p a c

data BasicFunction
instance CategoryLike BasicFunction where
  type CategoryLikeT BasicFunction a b = a -> b
  (>>>) = flip (.)

data BasicInt
instance CategoryLike BasicInt where
  type CategoryLikeT BasicInt Int Int = Int
  type CategoryConstraints BasicInt a b = (a ~ Int, b ~ Int)
  (>>>) = (+)

Итак, вот как теперь выглядит f: (я пишу это с явным forall, потому что это делает его кандидатом на использование TypeApplications)

f :: forall p a b c d. (
    CategoryLike p,
    CategoryConstraints p a b,
    CategoryConstraints p b c,
    CategoryConstraints p a c,
    CategoryConstraints p a d,
    CategoryConstraints p d b
  ) => CategoryLikeT p a d -> 
       CategoryLikeT p d b ->
       CategoryLikeT p b c ->
       CategoryLikeT p a c
f x y z = x >>> y >>> z

Чтобы использовать его, я могу сделать что-то вроде этого (выглядит на удивление красиво):

ghci> :set -XTypeApplications
ghci> :t f @BasicFunction (+1) id show
f @BasicFunction (+1) id show :: (Show a, Num a) => a -> [Char]
ghci> :t f @BasicInt 1 2 3
f @BasicInt 1 2 3 :: Int
person Alec    schedule 16.08.2016
comment
Ключевой вопрос заключается в том, можете ли вы написать f с сигнатурой типа? - person Clinton; 16.08.2016
comment
@Clinton Все еще не очень хорошая подпись, но немного лучше. По крайней мере, его можно элегантно использовать с приложением видимого типа. - person Alec; 16.08.2016