Синглтоны синглетонов (эмуляция сложных типов пи в Хаскеле) - PullRequest
0 голосов
/ 19 сентября 2018

У меня есть простое подтверждение концепции в Idris, которая использует зависимые типы для реализации некоторой не слишком сложной бизнес-логики.Несколько имен были изменены, чтобы защитить не очень невинных, но идея в том, что мы хотим собирать «строки» в последовательности.Каждая строка относится к определенному разделу, но только у одного (EconProduction) есть все, что нас интересует.В общем случае в строках есть ключевое слово, относящееся к разделу, и выражение, форма / тип которого может зависеть от используемого ключевого слова.

В этом конкретном разделе каждая строка описывает некоторые числа для «фазы» (Prod), или продолжает последнюю названную «фазу» (Continue).

В Idris мы можем сделать это так:

data EconSection
  = EconGeneral
  | EconProduction

data EconPhase
  = Oil
  | Water
  | NumPhase Nat

data ContState
  = ContNone
  | ContProd EconPhase

data Keyword : EconSection -> ContState -> ContState -> Type where
  Prod : (p : EconPhase) -> Keyword EconProduction c (ContProd p)
  Continue : Keyword s c c

data Expression : (s : EconSection) ->
                  (d : ContState) ->
                  Keyword s c d ->
                  Type where
  ExProc : Double -> Double -> Expression EconProduction (ContProd p) k

data Line : EconSection -> ContState -> ContState -> Type where
  L : (k : Keyword s c d) -> Expression s d k -> Line s c d

data Lines : EconSection -> ContState -> Type where
  First : Line s ContNone d -> Lines s d
  Then : Lines s c -> Line s c d -> Lines s d

infixl 0 `Then`

good : Lines EconProduction (ContProd (NumPhase 1))
good = First (L (Prod Oil) (ExProc 23.2 70.1))
      `Then` (L (Continue) (ExProc 27.9 1.2))
      `Then` (L (Prod (NumPhase 1)) (ExProc 91.2 7014.1))
      `Then` (L (Continue) (ExProc 91.2 7014.1))

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

{-# LANGUAGE GADTs, KindSignatures, DataKinds #-}
{-# LANGUAGE RankNTypes, TypeInType, TypeOperators #-}
{-# LANGUAGE TypeFamilies, TypeFamilyDependencies, MultiParamTypeClasses #-}

import Data.Kind (Type)

data Nat
  = Z
  | S Nat

data SNat :: Nat -> Type where
  SZ :: SNat 'Z
  SS :: SNat n -> SNat ('S n)

data SSNat :: forall (n :: Nat) . SNat n -> Type where
  SSZ :: SSNat 'SZ
  SSS :: SSNat n -> SSNat ('SS n)

type family SingNat (n :: Nat) :: SNat n where
  SingNat 'Z = 'SZ
  SingNat ('S n) = 'SS (SingNat n)

data EconSection
  = EconGeneral
  | EconProduction

data SEconSection :: EconSection -> Type where
  SEconGeneral :: SEconSection 'EconGeneral
  SEconProduction :: SEconSection 'EconProduction

type family SingSection (s :: EconSection) :: SEconSection s where
  SingSection 'EconGeneral = 'SEconGeneral
  SingSection 'EconProduction = 'SEconProduction 

data EconPhase
  = Oil
  | Water
  | NumPhase Nat

data SEconPhase :: EconPhase -> Type where
  SOil :: SEconPhase 'Oil
  SWater :: SEconPhase 'Water
  SNumPhase :: SNat n -> SEconPhase ('NumPhase n)

data SSEconPhase :: forall (p :: EconPhase) . SEconPhase p -> Type where
  SSOil :: SSEconPhase 'SOil
  SSWater :: SSEconPhase 'SWater
  SSNumPhase :: SSNat n -> SSEconPhase ('SNumPhase n)

type family SingEconPhase (p :: EconPhase) :: SEconPhase p where
  SingEconPhase 'Oil = 'SOil
  SingEconPhase 'Water = 'SWater
  SingEconPhase ('NumPhase n) = 'SNumPhase (SingNat n)

data ContState
  = ContNone
  | ContProd EconPhase

data SContState :: ContState -> Type where
  SContNone :: SContState 'ContNone
  SContProd :: SEconPhase p -> SContState ('ContProd p)

type family SingContState (c :: ContState) :: SContState c where
  SingContState 'ContNone = 'SContNone
  SingContState (ContProd p) = 'SContProd (SingEconPhase p)

data Keyword :: EconSection -> ContState -> ContState -> Type where
  Prod :: SEconPhase p -> Keyword 'EconProduction c ('ContProd p)
  Continue :: Keyword s c c

data SKeyword :: forall (s :: EconSection) (c :: ContState) (d :: ContState) .
                 Keyword s c d -> Type where
  SProd :: SSEconPhase p -> SKeyword ('Prod p)
  SContinue :: SKeyword 'Continue

data Expression :: forall (s :: EconSection) (c :: ContState) (d :: ContState) .
                   SEconSection s -> SContState d -> Keyword s c d -> Type where
  ExProc :: Double -> Double -> Expression SEconProduction (SContProd p) k

type family KWSection k where
  KWSection (Keyword s _ _) = s

type family KWFrom k where
  KWFrom (Keyword _ c _) = c

type family KWTo k where
  KWTo (Keyword _ _ d) = d

data Line :: EconSection -> ContState -> ContState -> Type where
  L :: SKeyword (k :: Keyword s c d)
    -> Expression (SingSection s) (SingContState d) k
    -> Line s c d

data Lines :: EconSection -> ContState -> Type where
  First :: Line s 'ContNone d -> Lines s d
  Then :: Lines s c -> Line s c d -> Lines s d

infixl 0 `Then`

good :: Lines 'EconProduction ('ContProd ('NumPhase ('S 'Z)))
good = First (L (SProd SSOil) (ExProc 23.2 70.1))
      `Then` (L (SContinue) (ExProc 27.9 1.2))
      `Then` (L (SProd (SSNumPhase (SSS SSZ))) (ExProc 91.2 7014.1))
      `Then` (L (SContinue) (ExProc 91.2 7014.1))

Вот мой вопрос.Есть ли способ избежать «синглетонов синглетонов»?Мне совсем не нравится внешний вид таких вещей, как SSNat и так далее, но именно здесь я получаю, переводя каждый тип пи в дополнительный слой одиночного воспроизведения.Я не смог заставить работать какой-нибудь более простой подход, и я не видел каких-либо умных идей в пакете singletons, чтобы сделать это проще, хотя я мог бы легко что-то пропустить под всем шаблоном Haskell.

1 Ответ

0 голосов
/ 19 сентября 2018

Да.

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

В вашем коде

С учетом вышесказанного мы можем удалить объявления SSNat и SSEconPhase из вашего кода.Затем в конструкторе SProd

SProd :: SSEconPhase p - > SKeyword ('Prod p)

Мы знаем, что SEconPhase будет достаточно для принятия решения p, поэтому мы можем переписать его как

SProd :: SEconPhase p - > SKeyword ('Prod p)

, что приводит кошибка типа - нам нужно преобразование типа, например

SomeType :: (p :: EconPhase) -> SEconPhase p

, которое вы уже определили в своем коде как SingEconPhase.В результате получается

SProd :: SEconPhase p - > SKeyword ('Prod (SingEconPhase p))

В общем

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

...