对于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)
此外,这个类比产生了一些有趣的新实例,例如函子 sum ( 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'
完全理解 的行为,universal 的时间复杂度splitAt
(如上所示)必须是Θ(length xs)
,foldl'
对于每个元素都是严格的,对吧?如果是,那一定是个问题,因为它不如普通版本的Θ(min n (length xs))
.