gelisam / surjective

An output coverage checker
https://hackage.haskell.org/package/surjective
11 stars 2 forks source link

Transform patterns within a subscope #9

Open gelisam opened 6 years ago

gelisam commented 6 years ago

Consider the following code:

listMaybeBools :: [Maybe Bool]
listMaybeBools = (Just <$> [True, False])
              ++ [Nothing]

There are two ways to make this surjective-friendly. We could list the three values separately:

listMaybeBools :: [Either Bool Bool]
listMaybeBools = $$(surjective
  [||\covers -> [ covers $ \(Just True)  -> Just True
                , covers $ \(Just False) -> Just False
                , covers $ \Nothing      -> Nothing
               ]
  ||])

Or we could split out the [True, False] bit into its own surjective-friendly definition:

listBools :: [Bool]
listBools = $$(surjective
  [||\covers -> [ covers $\True  -> True
                , covers $ \False -> Just False
                ]
  ||])

listMaybeBools :: [Either Bool Bool]
listMaybeBools = $$(surjective
  [||\covers -> ((\x -> covers $ \(Just _) -> Just x) <$> listBools)
             ++ [covers $ \Nothing -> Nothing]
  ||])

I personally like this second approach, but surjective shouldn't be in the business of recommending a coding style. To support the style of the original definition, we'd need to transform small patterns into bigger patterns:

listMaybeBools :: [Either Bool Bool]
listMaybeBools = $$(surjective
  [||\covers -> (Just <$> (transform covers $ \x -> \(Just x) -> \covers' -> [ covers' $ \True  -> True
                                                                            , covers' $ \False  -> False
                                                                            ]))
             ++ [covers $ \Nothing -> Nothing]
  ||])

Semantically, transform is another macro which, like surjective, gathers the calls to covers within its scope. Then, it transforms them (in this case from x to Just x), and passes them on to the surrounding surjective macro, who will gather both the patterns produced by transform (in this case Just True and Just False) and the other pattern defined outside of transform (in this case Nothing).

Since covers has type Covers a, transform must bind its own covers' function, of type Covers Bool.

Suggested by @rpglover64 in the same reddit comment as #8's suggestion.

rpglover64 commented 6 years ago

transform covers $ \x -> \(Just x) -> [...]

In the comment, I was careful that the second appearance of the variable was in an expression context, not in a pattern context; if both uses are in a pattern, the TH won't have the same name for both variables.

gelisam commented 6 years ago

I see! I'm a bit wary about using the (\x -> Just x) syntax because this would require converting an expression to a pattern, which would require slightly more maintenance as more patterns are added to the language. Hawk keeps getting broken as new syntax is added to haskell-src-exts for example, which is why I used the magic from Control.Lens.Plated this time.

gelisam commented 6 years ago

While the Names of the xs are indeed different, turns out their OccNames are the same.

rpglover64 commented 6 years ago

Hawk keeps getting broken as new syntax is added to haskell-src-exts for example

Would th-abstaction help?

turns out their OccNames are the same

ehh... I guess? That strikes me as more fragile, but I don't do much metaprogramming.

gelisam commented 6 years ago

th-abstraction indeed looks helpful for the kind of problems I am describing, but it looks like they aren't supporting patterns nor expressions yet.