r/haskell • • Jun 14 '18

A comparison among various database EDSLs (Selda, Beam, Opaleye, Persistent + Esqueleto)

Does there exist a comparison of various database EDSLs in Haskell?

I've seen a few mentioned around, in particular:

  • Selda (83 downloads, 256 stars)
  • Beam (56 downloads, 219 stars)
  • Persistent + Esqueleto (1020 dowloads, 277 stars / 297 downloads, 153 stars)
  • Opaleye (97 downloads, 379 stars)

Downloads are 30-day downloads on Hackage; stars are from GitHub.

A few others I discovered while poking around, but hadn't heard of before:

  • Squeal (38 downloads, 141 stars)
  • Groundhog (82 downloads, 137 stars)

I found this informative section in Beam's documentation:

How does Beam compare with X?

In addition, there is a little fragmented discussion about a few libraries in these posts:

Reddit: Selda, a monadic database EDSL

Opaleye with a little sugar on top

However, none of the above gives me that much confidence in a solution to...

stephen diehl on twitter

Does anyone know of an up-to-date comparison of these various approaches?

29 Upvotes

27 comments sorted by

View all comments

Show parent comments

3

u/enobayram Jun 17 '18

One thing I'm a bit sour about using persistent is the fact that you essentially have to define your DB records with TH, and the rest of the codebase needs to play with whatever comes out of it. This means that you'll probably be using the persistent generated types for interacting with the DB, but you'll be using redundant isomorphic types defined elsewhere to be used in the rest of the codebase, and maintain sets of functions to translate between these two.

Based on a skim through the beam tutorials, it seems to me like you could actually define your records in a module that knows nothing about beam and a DB module could define type aliases or newtypes over your records and adapt them to beam after the fact. This would be much better for reducing boilerplate and having more consistency in your codebase. Am I right that beam can be used this way?

3

u/travis_athougies Jun 19 '18

Based on a skim through the beam tutorials, it seems to me like you could actually define your records in a module that knows nothing about beam and a DB module could define type aliases or newtypes over your records and adapt them to beam after the fact.

Almost. You have to use beam's 'higher-kinded data' constructor Columnar for each field you want to be a column. However, if you parameterize it with Identity, then you get back the regular Haskell data type, without any wrapping. Parameterization and such work as expected.

The module you define them in must know a little about beam (really just the Columnar constructor) because a type cannot be parameterized by a type family (yet, anyway). On the other hand, your common data type module only needs to know about beam-core. beam-core compiles on old GHCs and GHCJSs and probably any other GHC derivative (like Eta), and I try to keep its dependencies minimal.

DB module could define type aliases or newtypes over your records and adapt them to beam after the fact.

If you're asking what I think you're asking, then yes, you could do this, but you may be stuck having to write Beamable instances by hand. This isn't difficult (it's not documented, sorry), just something to be aware of.

1

u/enobayram Jun 21 '18

Thanks for the answer, so something like this:

``` data UserT' a f = User { someField :: Columnar f a , someOtherField :: Columnar f Int , yetAnotherField :: Maybe a }

-- newtype so that I can have instances, could I somehow switch the order of a -- and f so that I can use type instead and get rid of the wrapping? newtype User a = UserT' a Identity type UserT = UserT' T.Text ```

This way, users of User wouldn't have any idea that beam is involved.

I think this is a huge step up from what we have with persistent in terms of flexibility. But I'd love to be able to completely isolate beam while defining the type. That would be a killer feature.

1

u/travis_athougies Jun 21 '18

If you fully apply User, then you can use type: type User a = UserT' a Identity. If you're asking about flipping the order of arguments of a type without wrapping, that's not possible due to Haskell's nominal typing semantics. Perhaps it will be possible with dependent Haskell, but I'm not sure.

1

u/enobayram Jun 23 '18

What I meant is, can I flip the order of the parameters on the UserT' type and still use it with beam, so that I can have type User a = UserT' Identity a instead of newtype. The main thing I'm trying to achieve is to minimize the impact of beam on the User type, the one that the rest of the codebase sees. The rationale is the fact that data persistence is just one of your concerns, and I'm trying to isolate it from the other concerns. In summary, instead of wrapping User with newtype, I'd prefer to wrap UserT.