Объявить тип для всех видов высшего порядка - PullRequest
1 голос
/ 08 мая 2019

У меня такое чувство, что я спрашиваю о невозможном, но здесь все идет.

Я хочу связать конструкторы типов с полностью примененной версией, которая нумерует параметры на уровне типа натуральными числами.Вот пример сеанса ghci с его желаемым использованием:

ghci> :kind! MKNumbered Maybe
MKNumbered Maybe :: *
= Maybe (Proxy Nat 1)
ghci> :kind! MKNumbered Either
MKNumbered Either :: *
= Either (Proxy Nat 1) (Proxy Nat 2)

Чтобы немного уменьшить шум вышеупомянутого, по сути, я получаю что-то вроде

Maybe  >----> Maybe 1
Either >----> Either 1 2 

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

type MkNumbered f n = UnU (MkNumbered_ (U f) 1 n)
type family MkNumbered_ (f :: k) (i::Nat) (n::Nat) :: j where
  MkNumbered_ (U f) i i = U (f (Proxy i))
  MkNumbered_ (U f) i n = MkNumbered_ (U (f (Proxy i))) (i+1) n

data U (a::k)
type family UnU f :: * where
  UnU (U f) = f

Тип U - это еще один прокси, который, по-видимому, необходим для получения желаемого поведения.Если у меня есть полностью примененный U, то есть U (a :: *), я могу развернуть его с помощью UnU.

Недостатком вышеизложенного является то, что, поскольку Proxy i :: *, MkNumbered может обрабатывать только конструкторы с * переменными.Нумерация

data A (f :: * -> *) a = ...

отсутствует, A (Proxy 1) (Proxy 2) не будет работать в аргументе Proxy 1.Я должен быть в состоянии улучшить MkNumbered, введя ряд конкретных прокси нумерации:

data NPxy1 (n :: Nat)
data NPxy2 (n :: Nat) (a :: i)
data NPxy3 (n :: Nat) (a :: i) (b :: j)
...

Это должно оставить меня с таким поведением:

ghci> :kind! MKNumbered A
MKNumbered A :: *
= A (NPxy2 Nat 1) (NPxy1 Nat 2)

Это очень помогает,только эти три определения NPxy, вероятно, охватывают большинство случаев более высокого порядка.Но мне было интересно, есть ли способ улучшить это, чтобы я мог охватить все k -> j -> ... -> * случаев?


Между прочим, я серьезно не надеюсь работать с такими типами, как

data B (b::Bool) = ...   

Мне нужно что-то вроде этого незаконного определения:

data NPxyBool (n :: Nat) :: Bool

В любомВ этом случае все типы Bool уже заняты.Идя дальше, я был бы рад узнать, что есть способ создать некоторые данные

data UndefinedN (n :: Nat) :: forall k . k

, которые я назвал UndefinedN, поскольку на добром уровне это кажется дном.


Редактировать: Использование по назначению

Суть моего предполагаемого использования - запросить тип для параметра прокси.

type family GetN s (a :: k) :: k 

GetN (Either Int Char) (Proxy 1) ~ Int

Однако мне также требуется, чтобыесли индекс прокси - это какой-то другой конкретный тип, кроме Proxy n, то этот тип просто возвращается.

GetN (Either Int Char) Maybe ~ Maybe

Однако любое решение семейства типов для Proxy n позволяет записывать экземпляры семейства для GetN с помощью Proxy n на лхс незаконно.Я открыт для решений на основе классов классов, где у нас может быть:

instance (Proxy n ~ pxy, GetNat s n ~ a) => GetN s pxy a where... 

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

Остальное это просто для информации, но, имея вышеизложенное, я должен иметь возможность получать субданные из моих типов параметров прокси.Например, заполнив мое определение A, выше:

data A f a = A { unA :: f (Maybe a) }

субданных в unA, так как пронумерованные параметры выглядят так:

type UnANums = (Proxy 1) (Maybe (Proxy 2))

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

type family GetNs s (ns :: k) :: k
GetNs (A [] Int) UnANums ~ [Maybe Int]
GetNs (A (Either String) Char) UnANums ~ Either String (Maybe Char)

В конечном итоге это приводит к генерации сигнатур обхода в общем.Учитывая исходный и целевой контексты, например, A f a и A g b, в общем представлении я буду иметь в K1 типы узлов, такие как UnANums, из которых я могу получить источник и цель, к которой необходимо перейти.

Ответы [ 2 ]

1 голос
/ 08 мая 2019

Как насчет этого:

{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
module SO56047176 where
import GHC.TypeLits
import Data.Functor.Compose -- for example

type family Proxy (n :: Nat) :: k

type Maybe_ = Maybe (Proxy 0)
type Either__ = Either (Proxy 0) (Proxy 1)
type Compose___ = Compose (Proxy 0) (Proxy 1) (Proxy 2)

Data.Functor.Compose принимает два параметра (->), но Proxy 0 и Proxy 1 по-прежнему работают.

0 голосов
/ 20 мая 2019

Я нашел решение по совокупности типов и семейств данных. Начиная с определения данных:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE PolyKinds #-}

import GHC.TypeLits hiding ( (*) )
import Data.Kind

class HasNProxyK j where
  data NProxyK (n :: Nat) (a::j) :: k
instance HasNProxyK Type where
  data NProxyK n a = NProxyK0
instance HasNProxyK k => HasNProxyK (j -> k) where
  data NProxyK n f = NProxyKSuc -- or NProxyKS (ProxyK n (f a))

Я объявляю класс типа HasNProxyK, для которого видов будут экземплярами. Связанные данные NProxyK ожидают Nat и некоторую переменную соответствующего вида j. Тип возвращаемого значения этого семейства данных будет другим, k.

Затем я создаю базовый падеж для Type (он же *) и индуктивный падеж для всех высших видов, которые в итоге приводят к виду с HasNProxyK.

Проверка этого в сеансе GHCI:

> :kind! NProxyK 3 Int
NProxyK 3 Int :: k
= NProxyK * k 3 Int

> :kind! NProxyK 3 (,,,,)
NProxyK 3 (,,,,) :: k
= NProxyK (* -> * -> * -> * -> * -> *) k 3 (,,,,)

Мы видим, что этот прокси почти готов. Lhs возвращаемого значения показывает, что тип имеет вид k, но параметр первого типа в rhs (который, я считаю, соответствует параметру класса) имеет соответствующий тип.

Мы могли бы указать на месте вызова подходящий тип для k, вместо этого я просто создал семейство типов, чтобы тип NProxyK соответствовал виду класса.

type family ToNProxyK (n :: Nat) (a :: k) :: k where
  ToNProxyK n (a :: Type) = NProxyK n a
  ToNProxyK n (a :: j -> k) = NProxyK n a

>:kind! ToNProxyK 1 (,,,,)
ToNProxyK 1 (,,,,) :: * -> * -> * -> * -> * -> *
= NProxyK
  (* -> * -> * -> * -> * -> *) (* -> * -> * -> * -> * -> *) 1 (,,,,)

Теперь Nat можно восстановить, используя что-то вроде следующего семейства:

type family LookupN (x :: k) :: Maybe Nat where
  LookupN (NProxyK n a) = Just n
  LookupN x             = Nothing

>:kind! (LookupN (ToNProxyK 3 Maybe))
(LookupN (ToNProxyK 3 Maybe)) :: Maybe Nat
= 'Just Nat 3
>:kind! (LookupN Maybe)
(LookupN Maybe) :: Maybe Nat
= 'Nothing Nat
...