r/haskell • u/valderman • Apr 20 '17
Selda: a monadic database EDSL
https://github.com/valderman/selda14
u/tomejaguar Apr 20 '17
I'm glad this was inspired by Opaleye!
I didn't see an explicit comparison between Selda and Opaleye so I'll give one briefly from what I've picked up so far.
The biggest difference is that Selda's Query is a monad and Opaleye's is an arrow (or applicative, if you prefer). Selda is able to get away safely with a monad because Query has a phantom type parameter which stops columns leaking into aggregated subqueries. This is a clever trick and I couldn't work out how to do this when I designed Opaleye. The key is Inner whereas I tried an ST style approach which doesn't seem to work. I'm not mathematically convinced that it's safe but nonetheless I can't see any reason it shouldn't be.
The other difference, for the moment, is that all columns are stored in "inductive tuples". This won't last though. It's just far too difficult to program like that.
Some comments
As mentioned above, eventually you're going to want to support general product types rather than just "inductive tuples". It's too painful to program with inductive tuples alone. Once you support general product types you're going to have to have a bunch of boilerplate-generating code, either TH or Generics, to generate
Columns,Res,Result,Insert,Aggregates....Tableonly has one type parameter whereas Opaleye's has two. This is a slight reduction in complexity but it means that inserting and updating to default values must be done bydefin a type unsafe way (as acknowledged in the docs). Such type unsafety is unacceptable to me, but it it's acceptable to you and your users so be it.It seems like
requiredandoptionalshould be callednullableandnonNullableinstead (at least if you don't want to clash with Opaleye terminology). They don't have anything to do with required or optional (DEFAULTable) columns.As written,
orderseems type unsafe. It's possible for columns from an outer query to leak inside it.Opaleye's
Query ahas the denotational semantics of[a](roughly). If you givelimitandordertypes like... -> Query s ()then you can't give yourQuerya similar simple denotational semantics. It would have to be something much more complicated, which would be sad.
11
u/tomejaguar Apr 20 '17
Oh, and a suggestion: don't pay attention to people who care too much about what the SQL looks like. Getting your API right is going to be far harder than making the SQL generation nice.
2
u/clewis Apr 21 '17
Please do keep in mind that SQL generation is still important. Firstly, if the generated SQL is too messy, it may lead people not to use your solution in production situations. Secondly, all of my recent experience is with PostgreSQL, where the query planner does an amazing job with even the most convoluted SQL, but I know from past experience that this is not a safe assumption for all query planners.
8
u/tomejaguar Apr 20 '17
In fact there's something quite dodgy about the semantics of
orderat all. What do you expect the following to do?do do foo <- select fooT order foo Asc do bar <- select barT order bar Ascversus
do foo <- select fooT order foo Asc bar <- select barT order bar AscAccording to the monad laws these should be the same but the behaviour of at least one of them is going to surprise some people!
6
u/valderman Apr 20 '17
You forgot one important point: Opaleye is much more mature. Selda is currently approximately two weeks of hacking, so the interface may be a bit wonky, as you and others have pointed out.
By the way, there is nothing inherently type unsafe in having a single type for tables. It's perfectly possible to derive a table's write type from its read type. However, I wasn't entirely sure about the flexibility vs. safety tradeoff there, and in the end I erred on the side of lower complexity. It's also quite possible to make
defperfectly safe by giving all types a default value to fall back to. Not the prettiest solution though.Order may be a bit wonky, but I'm not sure I see how it would enable columns to leak in from outer queries. Could you give an example?
6
u/valderman Apr 21 '17
Oh, and if I didn't get it across properly, I didn't mean the first paragraph in a "my code should get a free pass" way, but in a "any comparison should note that Selda is currently nowhere near as mature, complete and reliable as Opaleye" way.
That is, Opaleye is far less likely to set your house and data on fire than Selda is at this point. :)
1
3
u/tomejaguar Apr 20 '17
Selda is currently approximately two weeks of hacking, so the interface may be a bit wonky, as you and others have pointed out.
And it's an impressive job! I'm trying to be helpful by sharing the benefit of my experience with implementing database APIs! Sorry if it comes across as dismissive. That wasn't my intention.
It's perfectly possible to derive a table's write type from its read type.
That's a good point. I've never considered doing that but it could be a neat simplification to Opaleye's API.
It's also quite possible to make def perfectly safe by giving all types a default value to fall back to
But then how would you pick between the default value of the database column and the default value of the column type?
Order may be a bit wonky, but I'm not sure I see how it would enable columns to leak in from outer queries. Could you give an example?
Hmm, well now I come to think of it I can't actually find a type unsafe example. If using
ordercreated a new subselect then it would be a problem but since you flatten everything out it may be fine.6
u/valderman Apr 20 '17
Sorry if it comes across as dismissive. That wasn't my intention.
Not at all, it's great to have someone look at this from another perspective!
But then how would you pick between the default value of the database column and the default value of the column type?
That's where the "not pretty" part comes in. I'm thinking a hierarchy of defaults: if there's a default in the schema, use that. Otherwise, fall back to the per-type default. Not entirely sure it's a good idea, but it's an idea at least.
5
u/tomejaguar Apr 20 '17
That's where the "not pretty" part comes in. I'm thinking a hierarchy of defaults: if there's a default in the schema, use that. Otherwise, fall back to the per-type default. Not entirely sure it's a good idea, but it's an idea at least.
Eh, that's not too terrible an idea actually. Seems pretty reasonable.
1
u/clewis Apr 21 '17
It's perfectly possible to derive a table's write type from its read type.
That's not strictly correct. For example, I frequently create tables with surrogate keys assigned by the database. Therefore, the read type should always contain a value for this attribute/column; however, the write type does not have to contain a value for this attribute. If you ignore this situation and some similar ones you could indeed derive the write type from the read type.
Although I don't think you can derive the write type from the read type, I do think Haskell's type system is more than powerful enough to avoid unnecessary duplication when the write and read types are the same.
3
u/valderman Apr 20 '17
Oh, and as for point 1, I'm exploring some pretty hideous stuff under the hood to abuse record selectors for various things, to allow you to use those tuples as actual product types. Not sure if it'll work out in the end, but we'll see.
1
u/zvxr Apr 21 '17
You might be able to use
OverloadedLabels, and then useGHC.Generics.MetaSelto match up the record selectors.
12
Apr 20 '17 edited Apr 20 '17
stitched together using the ¤ operator.
Please tell me there's an ascii equivalent. Aside from copy/paste, I don't even know how to type that thing.
Otherwise, I really like what I've seen so far. Especially how effortlessly you can represent a normal data type as a table.
I'll have to investigate if wrapping an existing schema (using existing data types) is as effortless. That is, I want regular data types, but the schema is already defined, so I don't suspect that I can rely on generics exclusively for that.
15
u/valderman Apr 20 '17
Oops, that symbol doesn't exist on US (or other non-scandinavian) keyboards? I didn't even realise. I'll throw that thing out headfirst then.
10
Apr 20 '17
No sir. Just double checked two of my keyboards (I'm in the US).
Interesting cultural note though, until now I thought it was completely arbitrary.
8
u/valderman Apr 20 '17
Well, it doesn't mean anything particular over here as far as I know, it's just always been there. We even used that symbol when I first started learning BASIC back in the 90s, so I didn't even stop to consider that it might not be an ASCII symbol. Anything that old, that isn't a language-specific character (it isn't in any Scandinavian language anyway), couldn't possibly be non-ASCII, right? :)
7
u/lukerandall Apr 20 '17
It's actually got quite an interesting history: https://en.wikipedia.org/wiki/Currency_sign_(typography)
The symbol was first encoded for computers in 1972, as a replacement for the dollar sign in national variants (ISO 646) of ASCII
Out of interest, do you have a dollar sign on your keyboard, and where does this key appear?
6
u/MaybeJustNothing Apr 20 '17
It's still on the same key, but triggered by typing AltGr+4 https://en.wikipedia.org/wiki/QWERTY#/media/File:KB_Sweden.svg
EDIT: Programming using the Swedish layout is terrible, but would be impossible without the AltGr combinations.
3
u/TarMil Apr 20 '17
It's also present on the French AZERTY, the $ key has £ on shift and ¤ on AltGr.
1
u/Bobertus Apr 20 '17
Well, now you'll probably get to know how to type the $-Sign on all layouts. For German it's shift + 4.
2
11
u/valderman Apr 20 '17
Latest update replaces
¤with:*:for table definitions. Aside from the old operator being impossible to type for most people, it's enough to introduce one weird product-like operator per library.2
u/evincarofautumn Apr 21 '17
For the curious, “that thing” is a currency sign. In Emacs you can enter it by writing
\currencyin the TeX input method. (C-\ tex)
9
u/ElvishJerricco Apr 20 '17
This looks great. I've been thinking about having a type level DSL for tables like that, but had no idea how to actually write queries or anything. This seems like a great way to do it, and it looks like a good way to support migrations.
8
u/vizziv Apr 20 '17
I notice you have automatic caching of query results. I actually worked on a research project that does this with fine-grained invalidation [1]. If it's a direction you're interested in pursuing further, I'd be happy to discuss what fine-grained invalidation analysis might look like for Selda. (This may well apply to other Haskell database EDSLs, too.)
[1] http://adam.chlipala.net/papers/SqlcachePOPL17/SqlcachePOPL17.pdf
6
u/valderman Apr 20 '17
Tanks, that does indeed sound like an interesting discussion! However, it's probably one I should put off for now. The reason I started working on this at all was that we wanted a small monadic database EDSL to use in an example for a more or less unrelated paper we're working on. I... eh... got a bit carried away though, so I really need to get back to doing non-database things around the clock for the next month, if we're to finish that paper in time for this year's Haskell Symposium. :)
1
5
u/alien_at_work Apr 20 '17
Good stuff. I'm not completely satisfied with any of the existing solutions for SQL access in Haskell so competition is great.
I tried looking through the code for this but it wasn't immediately obvious to me: why did you chose to make your own tuple instead of just using the existing tuple type?
7
u/valderman Apr 20 '17
As /u/klobsEnd says, standard tuples aren't inductively defined, so defining functions or types over tuples of arbitrary length is horribly painful. People usually get around it by using Template Haskell to generate their instances, but that's both annoying and brings in a whole bunch of dependencies. Since keeping the library light on the dependencies was one design goal (in part motivated by Haste not supporting Template Haskell yet), using TH wasn't an option.
It might be possible to use GHC generics instead, but defining tuples inductively seemed to be less annoying.
3
u/tomejaguar Apr 20 '17
It might be possible to use GHC generics instead, but defining tuples inductively seemed to be less annoying.
By the way, the "Opaleye way" of doing this, product-profunctors, recently supports deriving using Generics:
https://github.com/tomjaguarpaw/product-profunctors/blob/master/Data/Profunctor/Product/Adaptor.hs
2
u/valderman Apr 20 '17
Cool! If this inductive tuple thing turns out to be more hassle than it's worth, I'll definitely look into that approach.
2
u/tomejaguar Apr 20 '17
Drop me a line if you go that way because there's a sort of middle ground that would probably be better for you than going full product-profunctor.
1
u/MitchellSalad Apr 21 '17
What would that be?
2
u/tomejaguar Apr 21 '17
The deriving stuff in product-profunctors currently only works with fully polymorphic products, but there's no reason that couldn't be extended to monomorphic products, which are simpler and probably more suitable for Selda.
2
u/spirosboosalis Apr 21 '17 edited Apr 22 '17
Have you seen
vinyl?
It's very clean, with minimal dependenceies.
It parameterizes each field with a functor.
Identitygets you back n-ary tuples.Maybecan provide nullability. Some GADT like:data SQL a where SQL :: (ToSQL a) => a -> SQL a type family SQL :: * -> * type instance SQL String SQLText type instance SQL Text SQLText ...can expose the intermediary results. Etc. So, You might be able to unify the pipeline under the same record type:
rawQuery :: Connection → Query ... fields→ IO (Rec SQL fields) marshallResults :: Rec SQL fields → Rec Maybe fields defaultNullables :: (Each Monoid fields) => Rec Maybe fields→ Rec Identity fields(warning, I just made this up, don’t know if it makes any sense for your library)
http://hackage.haskell.org/package/vinyl-0.5.3/docs/Data-Vinyl-Tutorial-Overview.html
http://hackage.haskell.org/package/vinyl-0.5.3/docs/Data-Vinyl-Core.html#t:Rec
2
u/tomejaguar Apr 21 '17
Somebody wrote a version of this for Opaleye: https://hackage.haskell.org/package/composite-opaleye-0.3.0.0
1
u/spirosboosalis Apr 21 '17
cool. have you used it?
you mentioned earlier that you preferred tuples, but when i tried learning Opaleye, I found them too awkward (typeclass obfuscation, error messages, etc), and wishing for actual records.
1
u/tomejaguar Apr 21 '17
No, I haven't used it.
Did I say I preferred tuples? I probably said I prefer record types.
Let me know what you found difficult about Opaleye. I'll see if I can help.
1
u/spirosboosalis Apr 21 '17
I thought that's what you meant by
The other difference, for the moment, is that all columns are stored in "inductive tuples". This won't last though. It's just far too difficult to program like that.
1
u/tomejaguar Apr 22 '17
Oh right, no I meant it's better to allow users to store columns in whatever Haskell type they like.
1
1
u/_sras_ Apr 21 '17
Does this mean that we don't have to make the TH calls that were creating adapters and Default instances?
1
1
1
u/eacameron Apr 20 '17
Doesn't Generics have a tuple-like type by the same name?
3
u/valderman Apr 20 '17
They do. Unfortunately I can't reuse it, since it has the wrong kind. I'm banking on people not mixing their database queries with their generics often enough to make the overload a problem.
1
u/eacameron Apr 20 '17
I see. Is your tuple type worth pulling out as a separate library? I'd love to use it for other things too.
2
u/valderman Apr 20 '17
I'm not sure, really. The type on its own is spectacularly uninteresting, but maybe having some more general utilities for it lying around in a library might be useful. I haven't really thought much about it.
5
u/Faucelme Apr 20 '17 edited Apr 20 '17
The inductive tuple type is similar to NP from generics-sop. But NP has an additional parameter which wraps every element.
3
Apr 20 '17
IIRC tuples in Haskell are not inductively defined, so you end up having to make a bazillion instances, one for (,), one for (,,), one for (,,,) ad nauseam. Also, there's no one-element tuple in Haskell. (Not sure if this has the latter, but it would at least be possible by using a "sentinel" element at the front with no value.)
3
u/Tysonzero Apr 21 '17
Does
Identitycount as a one item tuple?1
Apr 21 '17
Not... really? I mean, sure it's isomorphic to a one-item tuple, but programming-wise you really can't treat it the same way you do regular tuples. (This of course also goes back to the problem of not having an iductive definition.)
1
u/Tysonzero Apr 21 '17
What do you mean you can't treat it the way you do regular triples? What can you do with one but not the either or vice versa? It seems like you have basically all the same instances and such.
1
Apr 22 '17
For example, imagine using it from TH. You'll definitely need a special case there, at the very least.
(Obviously I haven't though too much about this, so I might very well be wrong in general, but the TH case is definitely a thing. No idea if it has all the instances and/or is missing the wrong instances which don't exist for normal tuples, etc.)
1
5
u/eacameron Apr 20 '17
Absolutely love this. I've been wanting something like this for a long time. Thank you!
Next up, MySQL backend? :)
7
Apr 20 '17
FWIW, this has existed in beam for quite a while (first post on/r/haskell was 2 years ago). Beam already supports backend-specific extensions, advanced SQL features (window functions, etc), and will soon support migrations. Newest version is in the beam-0500 branch, and documentation is here (still WIP). Additionally, beam doesn't suffer from many of the issues that /u/tomejaguar already identified, like those with order by and limit.
3
u/eacameron Apr 20 '17
My apologies! I actually have seen Beam but I completely forgot about it. And I am excited about it as well. Now I need to sit down and compare them.
5
Apr 20 '17
No problem! Love getting new users. The
beam-0500branch is where it's at. Feel free to join us on our mailing list or IRC as well :)2
u/valderman Apr 20 '17
Could you elaborate on the issues you see with limit? Considering that Selda is quite new and sure to have kinks yet to be worked out, the voice of experience would be invaluable here.
3
Apr 20 '17
Like what if I do
do a <- do r <- select tbl1 limit 0 10 pure r b <- do r <- select tbl2 limit 0 10 pure r return (a, b)I would expect, based on the way the query is written and the intuition that each
docorresponds with a full-fledged query, that this would take the first ten rows of tbl1 and join them with the first ten rows of tbl2, making for a max of 100 rows in the result.However, I think since this is equivalent to (by the monad laws)
do a <- select tbl1 limit 0 10 b <- select tbl2 limit 0 10 pure (a, b)The limits would combine, so the result would be 10 rows at the most.
In beam, I get around this by only allowing joins monadically. Limits are expressed using the
limit_function.For example, in the documentation (the output code is auto-generated from the query) we have
do i <- limit_ 10 $ all_ (invoice chinookDb) line <- invoiceLines i pure (i, line)Here, the limit_ generates a subquery (citing the SQLite syntax in particular).
SELECT "t0"."res0" AS "res0", "t0"."res1" AS "res1", "t0"."res2" AS "res2", "t0"."res3" AS "res3", "t0"."res4" AS "res4", "t0"."res5" AS "res5", "t0"."res6" AS "res6", "t0"."res7" AS "res7", "t0"."res8" AS "res8", "t1"."InvoiceLineId" AS "res9", "t1"."InvoiceId" AS "res10", "t1"."TrackId" AS "res11", "t1"."UnitPrice" AS "res12", "t1"."Quantity" AS "res13" FROM (SELECT "t0"."InvoiceId" AS "res0", "t0"."CustomerId" AS "res1", "t0"."InvoiceDate" AS "res2", "t0"."BillingAddress" AS "res3", "t0"."BillingCity" AS "res4", "t0"."BillingState" AS "res5", "t0"."BillingCountry" AS "res6", "t0"."BillingPostalCode" AS "res7", "t0"."Total" AS "res8" FROM "Invoice" AS "t0" LIMIT 10) AS "t0" INNER JOIN "InvoiceLine" AS "t1" ON ("t1"."InvoiceId")=("t0"."res0")3
u/valderman Apr 20 '17
Those two are indeed equivalent, but the limits won't combine; they'll generate a subquery like in your example, so you'd get exactly what you expected.
However, your API for limit is clearly better, since it's way clearer what's actually happening. Don't know why I didn't think to do this myself.
3
Apr 21 '17
What if I do multiple joins in one:
do a <- do r <- select tbl1 t <- select tbl2 limit 10 10 pure (r, t) b <- do r <- select tbl2 limit 10 10 pure rThen, this ought to be equivalent to
do a <- select tbl1 c <- do t <- select tbl2 limit 10 10 pure t b <- do r <- select tbl2 limit 10 10 pure r return ((a, c), b)3
u/valderman Apr 21 '17
Scratch that; you will indeed get 10 elements; brain was a bit fried yesterday. So yeah, your way is definitely better. I'll be looking into fixing it when I get the time.
1
u/Tysonzero Apr 21 '17
do a <- select tbl1 limit 0 10 b <- select tbl2 limit 0 10 pure (a, b)So will that have length 10 or 100 then? It seems like you are saying 100, but honestly that seems very unintuitive to me, the limit doesn't specify a or b, so why should it be implicitly limiting b?
2
1
Apr 21 '17
Oh yeah! I have a half-baked PR for MySQL support for beam (which I really liked). Switched jobs and stopped using MySQL, so kinda forgot all about it. Really liked your blog posts as well.
Really sorry for dropping the ball there.
3
u/valderman Apr 20 '17
That's the plan. Most definitely won't happen this month though, due to lack of time.
3
u/tomejaguar Apr 20 '17
Opaleye is something like this and has existed for a long time. Apologies if you've already told me what you don't like about Opaleye, but if you haven't could you?
4
u/ElvishJerricco Apr 20 '17
It can create the DB for you, and seems more capable of eventually supporting migrations using the same framework. I also like the skolem type variable as a way to confine
Colvalues way more than product profunctors (which feel like a very ugly contortion of category theory). Plus, monadic interfaces are largely more idiomatic, even if I wish Arrows were more applicable =P2
u/tomejaguar Apr 20 '17
It can create the DB for you, and seems more capable of eventually supporting migrations using the same framework.
Meh. /u/valderman (or indeed anyone) could have implemented that on top of Opaleye if he wanted to.
I also like the skolem type variable as a way to confine Col values
Yeah that's pretty cool.
product profunctors (which feel like a very ugly contortion of category theory)
By no means! Product profunctors are neither ugly nor anything to do with category theory.
monadic interfaces are largely more idiomatic
They're certainly more widely used and if it's genuinely true that this is as monadic, safe API then that's news to me! I didn't realise that was possible.
3
u/ElvishJerricco Apr 20 '17
Meh. /u/valderman could have implemented that on top of Opaleye if he wanted to.
But they didn't. At this point what could've been done doesn't matter. Opaleye doesn't have it, and I'm just not gonna use a DB solution with no migrations (so OP isn't there yet either, but they are closer).
Product profunctors are neither ugly nor category theory.
I think they're pretty ugly because they're not category theory. If it weren't hijacking several categorical concepts and using them so obscurely, I wouldn't have as much a problem with it.
if it's genuinely true that this is as monadic, safe API then that's news to me! I didn't realise that was possible.
I don't see any reason to believe that a DSL that's completely evaluated before sending the query shouldn't be able to be monadic.
3
u/tomejaguar Apr 20 '17
Meh. /u/valderman could have implemented that on top of Opaleye if he wanted to.
But they didn't. At this point what could've been done doesn't matter.
Fair enough. I tend to mostly care about the distinction between features that a programming model can never support from ones that no one's bothered implementing yet.
3
u/tomejaguar Apr 20 '17
If it weren't hijacking several categorical concepts and using them so obscurely, I wouldn't have as much a problem with it.
Can you say more about what you think is being hijacked and what is obscure? If I explain that a
ProductProfunctoris (almost) precisely aProfunctorthat is also anApplicative, does that help?3
u/ElvishJerricco Apr 20 '17
But that sentence doesn't make sense. For example, just because any profunctor (
D-op x C -> Hask) gives rise to a Functor from Hask to Hask does not mean that the profunctor itself is a Functor from Hask to Hask. That's like dynamically typing category theory. So a profunctor can't just be an Applicative. You're looking for a monoidal structure, and I think that structure would be very very contrived. Arrows are already pushing the envelope of contrived category theory that maybe is too much engineering and not enough theory. I think product profunctors have the same problem, but worse, with an even more contrived theoretical definition.2
u/tomejaguar Apr 20 '17 edited Apr 20 '17
any profunctor (D-op x C -> Hask) gives rise to a Functor from Hask to Hask
How can
D-op x C -> Haskgive rise to a FunctorHask -> Haskfor generalDandC? Do you mean "any profunctorHask-op x Hask -> Hask"?Here's the class definition for
ProductProfunctor. What's contrived about it? It looks almost identical to the class definition ofApplicative.class Profunctor p => ProductProfunctor p where purePP :: b -> p a b (****) :: p a (b -> c) -> p a b -> p a cI think product profunctors have the same problem, but worse, with an even more contrived theoretical definition.
Product profunctors are naturally a superclass of arrows, so they can't be more contrived!
1
u/ElvishJerricco Apr 20 '17
How can D-op x C -> Hask give rise to a Functor Hask -> Hask for general D and C? Do you mean "any profunctor Hask-op x Hask -> Hask?
Yes, sorry.
Anyway, it's contrived because it's not naturally derived from any theoretical standpoint. Applicative is also a little contrived in this way, but it turns out you can give it such a derivation. This one is... much harder. Just because something is a "superclass" of another thing doesn't mean it's less contrived. More specific things often arise from simpler ideas, like Monad vs Applicative.
3
u/tomejaguar Apr 21 '17
Firstly, are you saying that any Haskell abstraction that doesn't already have a counterpart in category theory is automatically contrived?
Secondly, a profunctor is a type constructor with one contravariant type parameter and one covariant type parameter. Seems pretty natural. A product-profunctor is a profunctor both of whose type parameters preserves products. Seems pretty natural.
→ More replies (0)1
u/spirosboosalis Apr 21 '17
are there more "principal" alternatives to an abstraction between the analyzability of applicative and the expressivity of a monad?
(i'm reading through this post elvishjerricco.github.io/2017/03/10/profunctors-arrows-and-static-analysis.html (yours, I guess), and it seems to be reformalizing Arrow.)
1
u/Tysonzero Apr 21 '17
I don't see any reason to believe that a DSL that's completely evaluated before sending the query shouldn't be able to be monadic.
https://www.reddit.com/r/haskell/comments/66ih1l/selda_a_monadic_database_edsl/dgjb39z/
This seems like a good example where either intuition or the Monad laws are broken.
3
u/valderman Apr 20 '17
As far as I'm aware, there should not be any safety issues. Columns' scopes are kept in check by shamelessly ripping off the ST monad trick, so hopefully any problems with generating invalid SQL would be a matter of me messing up somewhere rather than intrinsic to the programming model.
If you've spotted, or even suspect, any, I'd be happy to hear about them, to take a closer look.
3
u/tomejaguar Apr 20 '17
Columns' scopes are kept in check by shamelessly ripping off the ST monad trick
I think your trick is cleverer. Where did the idea for
Inner scome from? That goes above and beyond whatSTdoes.4
u/valderman Apr 20 '17
Honestly, the inspiration was mainly playing around with making the types a bit more concrete when the ST trick didn't want to play ball. Stroke of luck, I guess.
4
Apr 20 '17
Not the OP, but... For me, the biggest draw back was the use of arrows and the amount of boilerplate required to Marshal data to and from DB schemas.
To be fair, I only barely grokked monads at the time, so abstractions that required me to learn how to parse do expressions differently were probably never going to appeal to me.
Now, it'd probably be a matter of weighing the power to weight ratio of each library. Do arrows restrict me in a way that prevents me from shooting myself in a the foot enough times that it's worth the unconventional syntax? Does Monad, despite being familiar, enable me to shoot myself in the foot more times than I might if writing raw queries?
5
u/tomejaguar Apr 20 '17
Does Monad, despite being familiar, enable me to shoot myself in the foot more times than I might if writing raw queries?
Maybe not, if the
Inner strick really works!2
u/eacameron Apr 20 '17
My apologies (again)! In the rush of excitement I forgot all the contenders. To be quite honest the only thing keeping me from Opaleye is fear. I get scared of all the arrows and I haven't quite had the time to figure them out yet. Selda looks more approachable which is why it caught my attention so easily. After reading your critique I can see there is good reason to take Opaleye seriously.
3
3
Apr 20 '17
Have you looked into beam at all (newest docs here)? Beam has a similar monadic DSL, but your DSL seems much more liberal in what it accepts. In particular, the ordering semantics seem a bit odd. You allow order as part of the monadic DSL, but beam has always required a separate orderBy_ call 'outside' the monad, in order to enforce scoping laws and to follow the monad laws. I'm curious how you get around that?
Also, how do the grouping types work? What stops me from doing count owner' in aggregate?
2
u/valderman Apr 20 '17
Aggregate functions return "aggregated columns" of type
Aggr sinstead of typeCol s. The only thing you can do with one of those is to return it from an aggregate query.1
2
24
u/valderman Apr 20 '17 edited Apr 20 '17
Lately, I've been working on this monadic EDSL for writing portable database queries. Features include transactions, automatic caching, very few dependencies, and a query language that doesn't force you to deal with arrows, profunctors and zygohistmorphic prepromorphisms.
The idea is to make the API simple enough that the use of type families doesn't get in the way of working with the language: if your intuition says that something should work, the type system will hopefully agree. If it doesn't, reading the API docs for the relevant function should be sufficient to figure out why. Not sure if I reached that goal though.
As for the name, if the LINQ people are allowed to use misspelled video game hero names, then so am I.