morphismtech / squeal

Squeal, a deep embedding of SQL in Haskell
350 stars 32 forks source link

Writing utility query callers in Squeal 0.6 #210

Open adfretlink opened 4 years ago

adfretlink commented 4 years ago

I'm trying to upgrade our set of utilities to prepare the ground for a general upgrade of our codebase to Squeal 0.6. It's going rather well, this release has a lot of great features ! I'm very excited about it.

To spare some keystroke, I like to use a series of small functions like this one (Squeal 0.5):

-- | Run a query and retrieve its first result, that must be
-- an Only x. Use `queryStatement` to call this with a `Query`
-- rather than `Manipulation`.
maybeOneOnly ::
  (MonadUnliftIO m, MonadPQ schema m, ToParams p params, FromValue a b) =>
  Manipulation '[] schema params (OnlyReturns a)
  -- ^ Query to run, that returns a `Only` type
  -> p
  -- ^ Query to run
  -> m (Maybe b)
  -- ^ Maybe a result in the monadic context
maybeOneOnly q p = manipulateParams q p >>= (fmap . fmap) fromOnly . firstRow

I would have loved to be able to express it in Squeal 0.6 using the new type family Manipulation_, like this:

maybeOneOnly' ::
  (MonadUnliftIO m, MonadPQ db m) =>
  Manipulation_ db params (Only r)
  -- ^ Query to run, that returns a `Only` type
  -> params
  -- ^ Query to run
  -> m (Maybe r)
  -- ^ Maybe a result in the monadic context
maybeOneOnly' q p = manipulateParams q p >>= (fmap . fmap) fromOnly . firstRow

Sadly I must be missing something here, as the compiler yells at me:

    • Couldn't match type ‘Generics.SOP.Universe.Code params’
                     with ‘'[TupleCodeOf params (Generics.SOP.Universe.Code params)]’
        arising from a use of ‘manipulateParams’
    • In the first argument of ‘(>>=)’, namely ‘manipulateParams q p’
      In the expression:
        manipulateParams q p >>= (fmap . fmap) fromOnly . firstRow
      In an equation for ‘maybeOneOnly'’:
          maybeOneOnly' q p
            = manipulateParams q p >>= (fmap . fmap) fromOnly . firstRow
    • Relevant bindings include
        p :: params (bound at src/Fretlink/Squeal/Utils/Runners.hs:72:17)
        q :: Manipulation_ db params (Only r)
          (bound at src/Fretlink/Squeal/Utils/Runners.hs:72:15)
        maybeOneOnly' :: Manipulation_ db params (Only r)
                         -> params -> m (Maybe r)
          (bound at src/Fretlink/Squeal/Utils/Runners.hs:72:1)

Of course I can write it with less higher-level expressivity like this:

maybeOneOnly ::
  (MonadUnliftIO m, MonadPQ db m, GenericParams db params x xs, FromValue (NullPG r) r) =>
  Manipulation '[] db params (RowPG (Only r))
  -- ^ Query to run, that returns a `Only` type
  -> x
  -- ^ Query to run
  -> m (Maybe r)
  -- ^ Maybe a result in the monadic context
maybeOneOnly q p = manipulateParams q p >>= (fmap . fmap) fromOnly . firstRow

But it's not as nice ! Is there a way to write this kind of things with the handy Manipulation_ type-family or am I asking too much from the type system here ?

echatav commented 4 years ago

There shouldn't be a need to change these functions, either their types signatures or their bodies, to accommodate Manipulation_s or Query_s. That's because a Manipulation_ calculates a Manipulation.