【问题标题】:Haskell: How do I create a function that allows none, one or two Applicatives?Haskell:如何创建一个不允许、一个或两个 Applicative 的函数?
【发布时间】:2016-09-25 19:18:32
【问题描述】:

Control.Applicative.optional 允许处理零个或一个 Applicative。 many & some 分别允许 0 或更多,或 1 或更多。 我想创建一个专门处理零、一或二的函数。 签名可以是许多/一些,即

zeroOneOrTwo :: Alternative f => f a -> f [a]

我觉得这应该很简单,但我已经玩了一段时间了,无法让它工作。任何指针将不胜感激。

【问题讨论】:

  • 真正愚蠢的方式是zot f = sequenceA [f, f] <|> sequenceA [f] <|> sequenceA []

标签: haskell applicative alternative-functor


【解决方案1】:

这个怎么样:

zeroOneOrTwo :: Alternative f => f a -> f [a]
zeroOneOrTwo a = go (2 :: Int)
  where
    go n
      | n > 0 = ((:) <$> a <*> go (n - 1)) <|> pure []
      | otherwise = pure []

【讨论】:

  • 我宁愿翻转这两种情况:go 0 = pure []; go n = ...
  • 我会保持这种方式——检查n &gt; 0 防止在无效输入处无限下降——并且包装go 2,但直接将其导出为atMost :: Int -&gt; f a -&gt; f [a]
  • 谢谢@redneb,这正是我想要的,但不知何故无法让它发挥作用。非常感谢。
  • @leftaroundabout 是的,这就是我要追求的目标,确实是我的最终目标 - 但我什至无法使 0..2 案例最初工作。尽管有什么理由我们不能或不应该使用 Natural 而不是 Int 来消除至少一类错误?
  • @DanielWagner, Control.Monad.replicateM 现在只需要Applicative
【解决方案2】:

如果你要麻烦地限制这样的结果,你不妨让它的类型反映这一点。

data ZOT a = Zero | One a | Two a a

form :: a -> Maybe a -> ZOT a
form a Nothing = One a
form a (Just b) = Two a b

zeroOneOrTwo :: Alternative f => f a -> f (ZOT a)
zeroOneOrTwo a = (form <$> a <*> optional a) <|> pure Zero

如果您想要最多三个怎么办?还是最多四个?您可以通过几个语言扩展一次涵盖所有此类情况。

{-# LANGUAGE DataKinds, GADTs #-}

data Nat = Z | S Nat

data Natty n where
  Zy :: Natty 'Z
  Sy :: Natty n -> Natty ('S n)

data AtMost n a where
  Nil :: AtMost n a
  Cons :: a -> AtMost n a -> AtMost ('S n) a

atMost :: Alternative f => Natty n -> f a -> f (AtMost n a)
atMost Zy _ = pure Nil
atMost (Sy n) a = (Cons <$> a <*> atMost n a) <|> pure Nil

如果您不想使用任何花哨的扩展怎么办?好吧,它看起来不会那么漂亮,但如果你愿意,你仍然可以这样做,从 Ralf Hinze 的“Numerical Representations as Higher-Order Nested Datatypes”中获取一页。

data Z a = Z deriving (Show)
data S f a = Nil | Cons a (f a) deriving (Show)

class AtMost g where
  atMost :: Alternative f => f a -> f (g a)

instance AtMost Z where
  atMost _ = pure Z
instance AtMost g => (AtMost (S g)) where
  atMost m = (Cons <$> m <*> atMost m) <|> pure Nil

请注意,现在有两种不同的方法来构造空结果,ZNil,具有不同的类型。当结果与请求一样大时使用Z,而当结果不足时使用Nil

*AtMost> atMost (Just 3) :: Maybe ((S (S (S Z))) Int)
Just (Cons 3 (Cons 3 (Cons 3 Z)))

*AtMost> atMost Nothing :: Maybe ((S (S (S Z))) Int)
Just Nil

*AtMost> atMost undefined :: Maybe (Z Int)
Just Z

【讨论】:

  • 这是可能的,但 IMO 更合理的是保留列表作为结果并将最大长度作为额外参数。
  • 嗯! 抑制了说它也可以用完全依赖类型的方法来完成的动力,但很明显这无论如何都会出现......
  • 非常感谢@dfeuer。我曾简要考虑过这一点,甚至打算在问题中提及它——但鉴于我无法让基于列表的版本正常工作,这显然超出了我的考虑范围。但是现在你给了我一些东西让我去思考和学习它是如何工作的。非常感谢。
  • 如果我希望atMost 返回类型为f [a],我就不需要data AtMost,对吗?
  • @arrowd,redneb 的回答显示了如何做到这一点。我的目标是让结果的消费者使用已知的大小来避免无法到达的错误等。
猜你喜欢
  • 1970-01-01
  • 2015-02-21
  • 2020-02-08
  • 2021-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-20
  • 1970-01-01
相关资源
最近更新 更多