Универсальная конвертация контейнеров? если из складного в альтернативу?
За 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
,