Skip to content

Unalign instances for sequence types? (list, vector, etc.) #186

@LightAndLight

Description

@LightAndLight

I'm creating this issue because I thought that Unalign was missing instances for sequences, like list and vector. I'm technically wrong, according to the class's laws, but I think it's still worth discussing.

My intuition went like this: just as Unzip can be implemented for any Functor, it looks like Unalign can be implemented for any Filterable:

unalignDefault :: Filterable f => f (These a b) -> (f a, f b)
unalignDefault xs =
  ( mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) xs
  , mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) xs
  )

Despite my intuition, sequences don't satisfy one of the Unalign laws: uncurry align (unalign xs) ≡ xs. They do satisfy the other, however: unalign (align xs ys) ≡ (xs, ys).

While it's not a lawful Unalign implementation, I still feel like unalignDefault is a reasonable function, which is why I think this is worth talking about. unalign <-> uncurry align is currently specified as an isomorphism, which rules out sequence types, but it seems reasonable to only require unalign to be a left inverse of uncurry align.

Curiously, Unzip has a sort of "dual" version of this relaxation: unzip is only required be the right inverse of uncurry zip, because requiring them to form an isomorphism would rule out map types.


Proofs / working out

A quick proof for Unalign [] to check that my intuition is lawful:

uncurry align (unalign xs) ≡ xs:

forall (xs :: [a]).

uncurry align (unalign xs)
= { unalign definition }
uncurry align
  ( mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) xs
  , mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) xs
  )
= { uncurry definition }
align
  (mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) xs)
  (mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) xs)

induction on xs:

* xs@[]

  align
    (mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) [])
    (mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) [])
  = { mapMaybe _ [] }
  align [] []
  = { align definition }
  That <$> []
  = { (<$>) definition }
  []

* xs@(x : ys)
  uncurry align (unalign ys) = ys

  align
    (mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) (x : ys))
    (mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) (x : ys))
  = { mapMaybe definition }
  align
    (maybe id (:) ((\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) x) $ mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
    (maybe id (:) ((\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) x) $ mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)

  case analysis on x:

  * x@(These a b)

    align
      (maybe id (:) ((\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) (These a b)) $ mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
      (maybe id (:) ((\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) (These a b)) $ mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)
    = { beta reduction }
    align
      (maybe id (:) (Just a) $ mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
      (maybe id (:) (Just b) $ mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)
    = { maybe definition }
    align
      (a : mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
      (b : mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)
    = { align definition }
    These a b :
    align
      (mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
      (mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)
    = { unalign definition, uncurry definition }
    uncurry align (unalign ys)
    = { inductive hypothesis }
    ys

  * x@(This a)

    align
      (maybe id (:) ((\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) (This a)) $ mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
      (maybe id (:) ((\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) (This a)) $ mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)
    = { beta reduction }
    align
      (maybe id (:) (Just a) $ mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
      (maybe id (:) Nothing $ mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)
    = { maybe definition }
    align
      (a : mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
      (mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)

    (stuck)

  * x@(That b)

    align
      (maybe id (:) ((\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) (That b)) $ mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
      (maybe id (:) ((\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) (That b)) $ mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)
    = { beta reduction }
    align
      (maybe id (:) Nothing $ mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
      (maybe id (:) (Just b} $ mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)
    = { maybe definition }
    align
      (mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) ys)
      (b : mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) ys)

    (stuck)

It looks like it doesn't work out. A counterexample:

exists (xs :: [This a b]). uncurry align (unalign xs) != xs:

uncurry align (unalign [This 1, That "a", This 2, That "b", This 3])
=
uncurry align ([1, 2, 3], ["a", "b"])
=
[These 1 "a", These 2 "b", This 3]

How does this law work out for Map, then?

uncurry align (unalign [("k1", This 1), ("k2", That "a"), ("k3", This 2), ("k4", That "b"), ("k5", This 3)])
=
uncurry align ([("k1", This 1), ("k3", This 2), ("k5", This 3)], [("k2", That "a"), ("k4", That "b")])
=
[("k1", This 1), ("k2", That "a"), ("k3", This 2), ("k4", That "b"), ("k5", This 3)]

It works out because the Map "preserves keys"; each value has the same key before and after unaligning.

The other law does hold for lists: unalign (align xs ys) ≡ (xs, ys). Less formally:

  • unalign (align [] ys) ≡ ([], ys)
  • unalign (align xs []) ≡ (xs, [])
  • unalign (align (x : xs) (y : ys))
    =
    unalign (These x y : align xs ys)
    =
    ( mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) (These x y : align xs ys)
    , mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) (These x y : align xs ys)
    )
    =
    ( maybe id (:) ((\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) (These x y)) $ mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) (align xs ys)
    , maybe id (:) ((\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) (These x y)) $ mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) (align xs ys)
    )
    =
    ( maybe id (:) (Just x) $ mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) (align xs ys)
    , maybe id (:) (Just y) $ mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) (align xs ys)
    )
    =
    ( x : mapMaybe (\case; This a -> Just a; That _ -> Nothing; These a _ -> Just a) (align xs ys)
    , y : mapMaybe (\case; This _ -> Nothing; That b -> Just b; These _ b -> Just b) (align xs ys)
    )
    =
    ( x : fst (unalign (align xs ys))
    , y : snd (unalign (align xs ys))
    )
    =
    ( x : xs
    , y : ys
    )
    

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions