Проблемы с рефакторингом текущих типов (возможно, связанных с GADT/Type Families)

У меня есть такие типы:

-- There are codes
newtype ICode = ICode { fromICode :: String }
newtype RCode = RCode { fromRCode :: String }
data DCode = DCode1 | DCode2 | DCode3

-- There are locations described by type and code.
-- Current implementation looks like this:
data Location = LocType1 ICode
              | LocType2 ICode
              | LocType3 RCode
              | LocType4 DCode

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

С QuickCheck действительно легко продемонстрировать свойства, которые мне нужны Arbitrary и Эзона FromJSON экземпляры и еще одна функция. Первые 3 свойства необходимы для генерации правильных тестовых данных и 4-е для реализации бизнес-логики.

Я хотел бы иметь возможность:

  1. сделать произвольные экземпляры всех типов кода, таких как

        instance Arbitrary ICode where
          arbitrary = ...
        -- same with RCode and DCode
    
  2. делать Arbitrary экземпляры таких типов, как Location1 ICode(Это явно отличается от текущей реализации, и это то, что я пытаюсь исправить), которые описывают точное сочетание типа местоположения и типа кода. Location1 ICode может содержать только подмножество ICode возможные значения, поэтому я должен убедиться в этом.

  3. делать FromJSON экземпляры всех возможных типов, что-то в строках:

    instance FromJSON (Location a) where
      parseJSON = ...
    

    Необходимо десериализовать некоторые объекты json в зависимости от их значений.

  4. Некоторые функции должны работать только на одном типе местоположения. Это довольно неудобно в текущей реализации, потому что я должен использовать либо неполные функции, либо не совсем корректные возвращаемые типы, такие как Maybe, Я хотел бы иметь возможность сделать что-то вроде:

    location1IncludedInArbitraryLocation :: LocType1 -> Location a -> Bool
    location1IncludedInArbitraryLocation l = ...
    

Я полагаю, что решение лежит где-то на территории GADT /Data Families, но я не очень хорошо разбираюсь в этом типе фу. Если возможно несколько способов решить эту проблему, какой из них будет легче проверить / поработать позже?

1 ответ

Решение

3 и 4 кажутся несовместимыми. Это звучит как "резервный" механизм: используйте этот экземпляр, если более конкретного экземпляра нет. Вы можете получить это с помощью OverlappingInstances, но у меня всегда возникают проблемы с этим. Думаю, стоит попробовать.

Что касается остальной части вашей проблемы, кажется, что вы хотите Location быть гадом.

data LocType = Type1 | Type2 | Type3 | Type4

data Location :: LocType -> * where
    LocType1 :: ICode -> Location Type1
    LocType2 :: ICode -> Location Type2
    LocType3 :: RCode -> Location Type3
    LocType4 :: DCode -> Location Type4

Тогда вы можете легко сделать:

location1IncludedInArbitraryLocation :: Location Type1 -> Location t -> Bool
location1IncludedInArbitraryLocation (LocType1 icode) l = ...

Никакие другие случаи не должны быть определены здесь, потому что никакой другой конструктор не будет хорошо напечатан.

Надеюсь, этого достаточно, чтобы начать играть.

(потребности DataKinds, KindSignatures, GADTs)

Другие вопросы по тегам