Haskell: фильтрация гетерогенного списка по типу

Мы можем использовать парные последовательности для создания гетерогенных списков в Haskell:

type a *: b = (a, b)
a *: b = (a, b)
infixr 5 *:

hlist :: Int *: String *: Maybe Float *: ()
hlist = 1 *: "hello" *: Just 3 *: () -- (1, ("hello", (Just 3, ())))

Есть ли способ, который мы можем выполнить фильтрацию на уровне типов в этих списках? То есть определите некоторую полиморфную функцию hfilter такую, что для разных типов a, b и c:

hfilter :: a *: b *: c *: a *: b *: a *: () ->  a *: a *: a *: ()
hfilter :: a *: b *: c *: a *: b *: a *: () ->  b *: b *: ()
hfilter :: a *: b *: c *: a *: b *: a *: () ->  c *: ()
hfilter :: a *: b *: c *: a *: b *: a *: () ->  ()

Ответ 1

Это возможно с помощью нескольких расширений типов (как в сторону, пожалуйста, проверьте, что ваш пример кода компилируется при публикации вопросов. Мне пришлось внести немало поправок).

{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE OverlappingInstances #-}

type a :* b = (a, b)
a *: b = (a, b)
infixr 5 *:
infixr 5 :*

hlist :: Int :* String :* Int :* Maybe Float :* ()
hlist = 1 *: "hello" *: 2 *: Just 3 *: ()


class TypeFilter lst t where
    hfilter :: lst -> [t]

instance TypeFilter () t where
    hfilter _ = []

instance TypeFilter rest t => TypeFilter (t :* rest) t where
    hfilter (a, rest) = a : hfilter rest

instance TypeFilter rest t => TypeFilter (a :* rest) t where
    hfilter (_, rest) = hfilter rest

Теперь мы можем фильтровать элементы по типу, явно определяя тип нужного списка.

*Main> hfilter hlist :: [Int]
[1,2]
*Main> hfilter hlist :: [String]
["hello"]
*Main> hfilter hlist :: [Maybe Float]
[Just 3.0]
*Main> hfilter hlist :: [Maybe Int]
[]

Он работает, определяя многопараметрический тип-класс TypeFilter, который принимает тип гетерогенного списка и тип, который мы хотим отфильтровать. Затем мы определяем экземпляры для пустого списка /unit () и для списка, в котором тип соответствует (TypeFilter (t :* rest) t), и, наконец, для списка, где тип головы отличается от типа, который мы хотим получить (TypeFilter (a :* rest) t).

Обратите внимание, что в последнем случае в настоящее время нет способа показать, что a и t должны быть разных типов, но когда они одинаковы OverlappingInstances считает экземпляр TypeFilter (t :* rest) t более конкретным и выбирает его над TypeFilter (a :* rest) t.

Ответ 2

Хотя существуют способы делать то, что вы просите, существует очень высокая вероятность того, что вы не играете с силой Хаскелла. Не могли бы вы рассказать о своих потребностях? Обычно вы можете перечислить все варианты, которые вам понадобятся в алгебраическом типе данных. Затем ваш список будет однородным, позволяя вам сопоставлять шаблоны с элементами, которые будут работать на нем.