Универсальная конвертация контейнеров? если из складного в альтернативу?

За instance Alternative [], (<|>) = (++), Так я считала (<|>) как своего рода монтажное устройство, в результате чего получается практически универсальный контейнерный преобразователь:

-- (<|>) = generalization of (++)

(<|) :: Alternative f => x -> f x -> f x
x <| xs = pure x <|> xs

conv :: (Foldable t, Alternative f) => t x -> f x
conv = foldr (<|) empty

Действительно, я смог обобщить все функции из Data.Listвот некоторые из них:

-- fmap = generalization of map

reverse :: (Foldable t, Alternative f) => t a -> f a
reverse = getAlt . getDual . foldMap (Dual . Alt . pure)

-- asum = generalization of concat

asumMap :: (Foldable t, Alternative f) => (a -> f b) -> t a -> f b -- generalization of concatMap
asumMap f = getAlt . foldMap (Alt . f)

splitAt :: (Foldable t, Alternative f, Alternative g) => Int -> t a -> (f a, g a)
splitAt n xs = let (_,fs,gs) = foldl' (\(i,z1,z2) x -> if 0 < i then (i-1,z1 . (x <|),z2) else (0,z1,z2 . (x <|))) (n,id,id) xs in (fs empty,gs empty)

Кроме того, эта аналогия создает несколько интересных новых примеров, таких как рабочий аппликативный функтор для сумм функторов (Data.Functor.Sum):

instance (Foldable f, Applicative f, Alternative g) => Applicative (Sum f g) where
    pure = InL . pure
    InL f <*> InL x = InL (f <*> x)
    InL f <*> InR x = InR (conv f <*> x)
    InR f <*> InL x = InR (f <*> conv x)
    InR f <*> InR x = InR (f <*> x)

instance (Foldable f, Applicative f, Alternative g) => Alternative (Sum f g) where
    empty = InR empty
    InL x <|> _ = InL x
    InR _ <|> InL y = InL y
    InR x <|> InR y = InR (x <|> y)

Это действительно хорошая идея, чтобы обобщить все функции и создать новые экземпляры по этой аналогии, особенно для операций со списками?

РЕДАКТИРОВАТЬ: Я особенно обеспокоен неоднозначным типом возврата. Для обычных операций со списком возвращаемый тип выводится из типов аргументов. Но "универсальной" версии нет, так как тип возвращаемого значения должен быть указан явно. Достаточно ли серьезна эта проблема, чтобы считать эту аналогию опасной? (Или есть еще какие-то проблемы?)

РЕДАКТИРОВАТЬ 2: Если я понимаю поведение foldl' собственно, сложность времени универсальная splitAt (показано выше) должно быть Θ(length xs), как foldl' строго для каждого элемента, верно? Если да, то это должно быть проблемой, так как она уступает обычной версии Θ(min n (length xs)),

1 ответ

Решение

Не всегда хорошая идея сделать функции полиморфными настолько теоретически, насколько это возможно, в частности, не аргументы функций. Как правило: сделать результаты функции как можно более полиморфными. (Часто аргументы уже содержат некоторые переменные типа, которые используются в результате.) Только если у вас есть конкретная причина, также дайте аргументам дополнительный полиморфизм.

Причина в том, что если все полиморфно, у компилятора нет подсказок относительно того, какие конкретные типы выбрать. Полиморфные результаты / значения обычно в порядке, потому что они, как правило, прямо или косвенно связаны с определением верхнего уровня, которое имеет явную сигнатуру, но полиморфные аргументы часто будут заполняться только литералами (литералы чисел полиморфны в Haskell, а строки - / списки тоже могут быть) или другими полиморфными значениями, так что в конечном итоге вам придется набирать много явных локальных подписей, что, как правило, более неудобно, чем необходимость иногда добавлять явную функцию преобразования, потому что что-то недостаточно полиморфно.

Эта идея с Foldable->Alternative в частности, есть еще одна проблема, Alternative класс довольно неодобрительный, не имеющий серьезной математической поддержки. Это в основном класс аппликативных функторов, которые для каждого экземпляра порождают Monoid, Ну, это также может быть выражено напрямую, требуя Monoid сам. Таким образом, "универсальная функция преобразования контейнеров" уже существует, она foldMap pure,

Другие вопросы по тегам