Это возможно с несколькими расширениями типов (кроме этого, пожалуйста, проверьте, что ваш пример кода компилируется при публикации вопросов. Мне пришлось внести немало исправлений).
{-# 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
, который принимает тип гетерогенного списка и тип, по которому мы хотим фильтровать. Затем мы определяем экземпляры для пустого списка / блока ()
и для списка, в котором тип соответствует (TypeFilter (t :* rest) t
), и, наконец, для списка, в котором тип головы отличается от типа, который мы хотим получить (TypeFilter (a :* rest) t)
.
Обратите внимание, что в последнем случае в настоящее время нет способа указать, что a
и t
должны быть разных типов, но когда они совпадают OverlappingInstances
считает экземпляр TypeFilter (t :* rest) t
как более конкретный и выбирает его над TypeFilter (a :* rest) t
.