`These` `Applicative`: preserve more failure info?

Analyzing the Applicative Instance of the These Datatype

The These datatype from the these package has an Applicative instance:

instance (Semigroup a) => Applicative (These a) where
    pure = That
    This  a   <*> _         = This a
    That    _ <*> This  b   = This b
    That    f <*> That    x = That (f x)
    That    f <*> These b x = These b (f x)
    These a _ <*> This  b   = This (a <> b)
    These a f <*> That    x = These a (f x)
    These a f <*> These b x = These (a <> b) (f x)

When one of the These is a This, the result is always This. However, there appears to be an assymetry when one of the These is a These constructor. The information from the These constructor is either preserved in the result or discarded, depending on which one is the argument.

    This  a   <*> _         = This a
These a _ <*> This  b   = This (a <> b)

Testing this in ghci:

ghci> This "a" <*> These "b" True
This "a"
ghci> These "a" not <*> This "b"
This "ab"

What would the consequence be if we added a case like

This  a <*> These b _    = This (a <> b)

to the Applicative instance? Would this break the Applicative laws?

Adding the case This a <*> These b _ = This (a <> b) to the Applicative instance of These would not break the Applicative laws.

The Applicative laws state that the pure function should behave as an identity under (<*>) and that (<*>) should be associative. Let’s analyze these laws with the newly added case:

  1. Identity Law: pure id <*> v = v

    • When v is This x, the result would be This x since This x is always returned when This is the first argument in the existing implementation.
    • When v is That y, These y z, or These y z, the result would be the same as before since the existing cases already handle these scenarios correctly.
    • Hence, the identity law would still hold.
  2. Associative Law: u <*> (v <*> w) = (pure (.) <*> u <*> v) <*> w

    • When u is This x, v is This y, and w is This z, the result would be This (x <> (y <> z)) in both cases, since (x <> (y <> z)) is the same as ((x <> y) <> z).
    • When u is This x, v is This y, and w is That z or These z w, the result would be This (x <> y) in both cases, since (x <> y) is the same as ((x <> y) <> z).
    • This is consistent with the existing behavior of the other cases, so the associative law would still hold.

Therefore, adding the case This a <*> These b _ = This (a <> b) would not break the Applicative laws.