Семейства типов Haskell и фиктивные аргументы
-
19-09-2019 - |
Вопрос
Я сделал функцию, похожую на numpy array
.Он преобразует списки в массивы, списки списков в 2d-массивы и т. д.
Это работает следующим образом:
ghci> arrFromNestedLists ["hello", "world"] :: Array (Int, (Int, ())) Char
array ((0,(0,())),(1,(4,()))) [((0,(0,())),'h'),((0,(1,())),'e'),((0,(2,())),'l'),((0,(3,())),'l'),((0,(4,())),'o'),((1,(0,())),'w'),((1,(1,())),'o'),((1,(2,())),'r'),((1,(3,())),'l'),((1,(4,())),'d')]
(Int, (Int, ()))
и не (Int, Int)
потому что я не знаю программного способа увеличения длины кортежа.(побочный вопрос:есть такой способ?)
Его кодирование было неудобным, и мне пришлось использовать «обходной путь» (передавать фиктивные аргументы функциям), чтобы это работало.Интересно, есть ли лучший способ?
Итак, вот код, прерываемый подробностями неприятных обходных путей:
{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, TypeFamilies #-}
type family ListOfIndex i a
type instance ListOfIndex () a = a
type instance ListOfIndex (Int, i) a = [ListOfIndex i a]
class Ix i => ArrConv i where
acBounds :: a -> ListOfIndex i a -> (i, i)
acFlatten :: i -> ListOfIndex i a -> [a]
acBounds
"должно быть :: ListOfIndex i a -> (i, i)
.И аналогично для acFlatten
.Каждому присвоена фиктивная переменная (undefined
всегда заданное значение), потому что иначе я не смог бы его скомпилировать :(
arrFromNestedLists :: forall i a. ArrConv i => ListOfIndex i a -> Array i a
arrFromNestedLists lst =
listArray
(acBounds (undefined :: a) lst)
(acFlatten (undefined :: i) lst)
Выше это манекен undefined
спор проходит на работе.Он сообщает GHC, какой экземпляр ListOfIndex
использовать.
instance ArrConv () where
acBounds _ = const ((), ())
acFlatten _ = (: [])
Приведенная ниже функция должна была быть acBounds
функция в экземпляре ArrConv
, и объявлен снаружи только потому, что мне нужно использовать ScopedTypeVariables
и я не знаю, как это сделать в функции в определении экземпляра..
acSucBounds
:: forall a i. ArrConv i
=> a -> [ListOfIndex i a] -> ((Int, i), (Int, i))
acSucBounds _ lst =
((0, inStart), (length lst - 1, inEnd))
where
(inStart, inEnd) = acBounds (undefined :: a) (head lst)
instance ArrConv i => ArrConv (Int, i) where
acBounds = acSucBounds
acFlatten _ = concatMap (acFlatten (undefined :: i))
Решение
Причина, по которой необходимы дополнительные аргументы для acBounds и acFlatten, заключается в том, что типы a
и i
невозможно восстановить из ListOfIndex i a -> (i, i)
и ListOfIndex i a -> [a]
соответственно.Одним из обходных путей является объединение двух методов в один метод. acArgs
типа ListOfIndex i a -> ((i, i), a)
.Теперь единственная проблема состоит в том, чтобы использовать его в экземпляре (Int, i)
таким образом, чтобы средство проверки типов не слишком обобщало свой тип, вызывая ту же проблему, что и раньше (например, мы не можем просто использовать fst . acArgs
).
{-# LANGUAGE TypeFamilies, FlexibleInstances #-} import Data.Array type family ListOfIndex i a type instance ListOfIndex () a = a type instance ListOfIndex (Int, i) a = [ListOfIndex i a] class Ix i => ArrConv i where acArgs :: ListOfIndex i a -> ((i, i), [a]) instance ArrConv () where acArgs x = (((), ()), [x]) instance ArrConv i => ArrConv (Int, i) where acArgs lst = (((0, inStart), (length lst - 1, inEnd)), args >>= snd) where args = map acArgs lst (inStart, inEnd) = fst (head args) arrFromNestedLists :: ArrConv i => ListOfIndex i a -> Array i a arrFromNestedLists = uncurry listArray . acArgs
Другие советы
Если вы хотите сохранить acBounds
и acFlatten
отдельно, вы можете добавить аргумент тега уровня типа к этому, т.е. acBounds
будет иметь тип acBounds :: Proxy a -> ListOfIndex i a -> (i, i)
.Это устраняет необходимость в undefined
аргументы, так как вы можете просто передать (Proxy :: SomeConcreteType)
к этому;и acBounds
не имеет возможности извлечь из него какую-либо полезную информацию на уровне значений, поскольку он изоморфен (нетипизированным образом) типу модуля.