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)
通常使用所有函数进行概括,并使用此类比法创建新实例,尤其是列表操作?
编辑:我特别关注模棱两可的回归类型。对于常规列表操作,返回类型可从其参数类型中进行推导。但是&#34;通用&#34;版本不是,因为必须显式指定返回类型。这个问题严重到足以使这个比喻变得危险吗? (或者还有其他问题吗?)编辑2:如果我完全理解foldl'
的行为,则通用splitAt
(如上所示)的时间复杂度必须为Θ(length xs)
,{{1}对每个元素都严格,对吧?如果是,那一定是个问题,因为它不如普通版foldl'
。
答案 0 :(得分:3)
将函数设置为理论上可能的多态并不总是一个好主意,尤其不是函数参数。根据经验:将函数结果尽可能多态化。 (通常,参数将包含一些在结果中使用的类型变量。)只有在您有特定原因的情况下,还要赋予参数额外的多态性。
原因是:如果所有都是多态的,编译器没有关于选择哪些具体类型的提示。多态结果/值通常是正确的,因为它们通常直接或间接绑定到具有显式签名的某个顶级定义,但多态参数通常只用文字填充(数字文字是Haskell中的多态,并且字符串/列表也可以是)或其他多态值,因此您最终必须键入大量显式本地签名,这往往比偶尔抛出显式转换函数更尴尬,因为有些东西是不够多态。
Foldable->Alternative
的这个想法明确地存在另一个问题,Alternative
类相当不受欢迎,没有非常坚实的数学支持。它基本上是应用函子的类,对于每个实例化都会产生Monoid
。嗯,这也可以通过要求Monoid
本身直接表达。因此,“通用容器转换功能”已经存在,它是foldMap pure
。