简体   繁体   English

避免模​​式匹配不完整

[英]Avoiding an incomplete pattern match

Consider the following code: 考虑以下代码:

data A
data B

f :: A -> B
f = undefined

data T = TA A | TB B
data ListT = ListTA [A] | ListTB [B]

g :: [T] -> ListT
g l = 
  let
    f' :: T -> B
    f' (TA x) = f x
    f' (TB x) = x
    isA :: T -> Bool
    isA TA{} = True
    isA TB{} = False
  in
    case (all isA l) of
      True -> ListTA (map (\(TA x) -> x) l)
      False -> ListTB (map f' l)

main = pure ()

The idea behind this is I've got a list of either A s or B s mixed together. 这背后的想法是我得到了AB混合在一起的列表。 I can convert A -> B but not the other way around. 我可以转换A -> B但不能反过来。 Based on this list, I want to make either a list of A s or list of B s, the former if all my original list elements are A s, the latter if at least one is a B . 基于此列表,我想创建一个A列表或B列表,如果我所有原始列表元素都是A ,则前者,如果至少一个是B ,则后者。

The above code compiles (and I'm guessing will work) but the incomplete pattern match in the map (\\(TA x) -> x) l makes me just a little uncomfortable. 上面的代码可以编译(并且我猜想会起作用),但是map (\\(TA x) -> x) l不完整的模式匹配map (\\(TA x) -> x) l使我有点不舒服。 Is such an incomplete match just a necessity of what I'm doing here? 如此不完整的匹配仅仅是我在这里所做的必要吗? Also, am I reinventing the wheel, is there something that generalises what I'm doing here? 另外,我是否正在重新发明轮子,是否可以概括一下我在这里所做的事情?

The only way I can think of is something like 我能想到的唯一方法是

tryA :: [T] -> Maybe [A]
tryA [] = []
tryA (t:ts) =
  case t of
    TA x -> do xs <- tryA ts; return (x:xs)
    TB _ -> Nothing

If tryA returns nothing, then do map f' l as before. 如果tryA返回任何内容,则像以前一样map f' l tryA

This way you're doing the all isA l and the map in a single pass, and it avoids an incomplete pattern. 这样,您可以一次完成all isA lmap ,并且避免了不完整的模式。

I'd structure it like this: build two lists - one full of A s and one full of B s - with the effect that building the list of A s could fail. 我会这样构造:建立两个列表-一个完整的A列表和一个完整的B列表-结果是建立A列表可能会失败。 One can build a Monoid which implements this logic and foldMap into it. 可以构建一个Monoid来实现此逻辑并将foldMap嵌入其中。

Since one could fail to build a list of A s, we'll need to build this Monoid on top of Maybe . 由于可能无法建立A的列表,因此我们需要在Maybe顶部建立此Monoid The behaviour we want comes from Maybe 's Applicative instance: if either of mappend 's arguments is Nothing then the whole thing fails, otherwise we want to use mappend to combine the two results. 我们想要的行为来自MaybeApplicative实例:如果mappend的参数之一为Nothing那么整个操作都会失败,否则,我们想使用mappend来组合这两个结果。 This is a general recipe for combining an Applicative and a Monoid . 这是组合ApplicativeMonoid的一般方法。 Concretely: 具体来说:

newtype WrappedApplicative f a = Wrap { unWrap :: f a }

instance (Applicative f, Monoid m) => Monoid (WrappedApplicative f m) where
    mempty = pure mempty
    Wrap x `mappend` Wrap y = Wrap $ liftA2 mappend x y

I don't know if this newtype is somewhere in base . 我不知道这newtype在某处base It seems like the sort of thing that would be there but I couldn't find it. 似乎会有东西出现,但我找不到。

Without further ado, here's the Monoid we'll be foldMap ping into: foldMap ,这是Monoid我们将foldMap ping到:

type Result = ([B], WrappedApplicative Maybe [A])

I'm borrowing (a, b) 's Monoid instance , which delegates in parallel to a and b 's Monoid instances. 我要借用(a, b)Monoid实例 ,该实例并行地委托给abMonoid实例。

getAsOrToBs :: [Either A B] -> Either [A] [B]
getAsOrToBs = fromResult . foldMap toResult
    where toResult (Left a) = ([aToB a], Wrap (Just [a]))
          toResult (Right b) = ([b], Wrap Nothing)
          fromResult (_, Wrap (Just as)) = Left as
          fromResult (bs, Wrap Nothing) = Right bs

Alternatively, with foldr : 或者,使用文件foldr

getAsOrToBs :: [Either A B] -> Either [A] [B]
getAsOrToBs = fromResult . foldr f ([], Just [])
    where f (Left a) (bs, mas) = (aToB a : bs, fmap (a:) mas)
          f (Right b) (bs, _) = (b:bs, Nothing)
          fromResult (_, Just as) = Left as
          fromResult (bs, Nothing) = Right bs

Look, ma, no partial functions! 瞧,妈,没有局部功能!

After some help from the other answers, I'm going to answer my own question for the benefit of future viewers. 在获得其他答案的帮助之后,我将回答自己的问题,以使将来的观众受益。 I believe the most suscinct function for g is as follows (and noticed I've generalised to Traversable instead of just lists). 我相信g的最明显的功能如下(并注意到我已将其推广到Traversable而不是列表)。

data ListT t = ListTA (t A) | ListTB (t B)

g :: (Traversable t) => t T -> ListT t
g l = 
  let
    f2B :: T -> B
    f2B (TA x) = f x
    f2B (TB x) = x
    f2A :: T -> Maybe A
    f2A (TA x) = Just x
    f2A (TB x) = Nothing
  in
    maybe (ListTB (fmap f2B l)) ListTA (traverse f2A l)

main = pure ()

On lists, this should only take space proportional to the number of leading A s, which is minimal I believe. 在列表上,此空间只应与前导A的数量成比例,我认为这是最小的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM