Ответ 1
Кажется, что индекс в тип является индексом в набор конструкторов, следующий индексом в произведение, представляющее этот конструктор. Это может быть осуществлено вполне естественно, например, generics-sop.
Сначала вам нужен тип данных для представления возможных индексов в один элемент продукта. Это может быть индекс, указывающий на элемент типа a
,
или индекс, указывающий на что-то типа g b
- для которого требуется указатель, указывающий на g
, и указатель, указывающий на элемент типа a
в b
. Это кодируется следующим типом:
import Generics.SOP
data ArgIx f x x' where
Here :: ArgIx f x x
There :: (Generic (g x')) => Ix g -> ArgIx f x x' -> ArgIx f x (g x')
newtype Ix f = ...
Сам индекс представляет собой сумму (реализуемую NS
для n-арной суммы) сумм по общему представлению типа (выбор конструктора, выбор элемента конструктора):
newtype Ix f = MkIx (forall x . NS (NS (ArgIx f x)) (Code (f x)))
Вы можете написать интеллектуальные конструкторы для различных индексов:
listIx :: Natural -> Ix []
listIx 0 = MkIx $ S $ Z $ Z Here
listIx k = MkIx $ S $ Z $ S $ Z $ There (listIx (k-1)) Here
treeIx :: [Bool] -> Ix Tree
treeIx [] = MkIx $ S $ Z $ S $ Z Here
treeIx (b:bs) =
case b of
True -> MkIx $ S $ Z $ Z $ There (treeIx bs) Here
False -> MkIx $ S $ Z $ S $ S $ Z $ There (treeIx bs) Here
roseIx :: [Natural] -> Ix Rose
roseIx [] = MkIx $ Z $ Z Here
roseIx (k:ks) = MkIx $ Z $ S $ Z $ There (listIx k) (There (roseIx ks) Here)
Обратите внимание, что, например, в списке вы не можете построить индекс (non-bottom), указывающий на конструктор []
, аналогично для Tree
и Empty
, или конструкторы, содержащие значения, тип которых не является a
, или что-то, содержащее некоторые значения тип a
. Количественная оценка в MkIx
предотвращает создание плохих вещей, таких как индекс, указывающий на первый Int
в data X x = X Int x
, где x
создается на Int
.
Реализация функции индекса довольно проста, даже если типы страшны:
(!) :: (Generic (f x)) => f x -> Ix f -> Maybe x
(!) arg (MkIx ix) = go (unSOP $ from arg) ix where
atIx :: a -> ArgIx f x a -> Maybe x
atIx a Here = Just a
atIx a (There ix0 ix1) = a ! ix0 >>= flip atIx ix1
go :: (All SListI xss) => NS (NP I) xss -> NS (NS (ArgIx f x)) xss -> Maybe x
go (Z a) (Z b) = hcollapse $ hzipWith (\(I x) -> K . atIx x) a b
go (S x) (S x') = go x x'
go Z{} S{} = Nothing
go S{} Z{} = Nothing
Функция go
сравнивает конструктор с указателем и фактическим конструктором, используемым типом. Если конструкторы не совпадают, индексирование возвращает Nothing
. Если это так, выполняется фактическое индексирование - что тривиально в том случае, если индекс точно указывает Here
, а в случае какой-либо подструктуры обе операции индексирования должны выполняться один за другим, который обрабатывается >>=
,
И простой тест:
>map (("hello" !) . listIx) [0..5]
[Just 'h',Just 'e',Just 'l',Just 'l',Just 'o',Nothing]