folsen / opaleye-gen

A lightweight program to generate Opaleye boilerplate from a database
BSD 3-Clause "New" or "Revised" License
13 stars 9 forks source link

Generate type synonym with all type variables under Maybe #4

Closed expipiplus1 closed 8 years ago

expipiplus1 commented 8 years ago

Take for example the type

data Book' c1 c2 c3 = 
  Book
    { bookAuthor :: c1
    , bookText :: c2
    , bookBlurb :: c3
    }

type Book = Book' Text Text (Maybe Text)

type BookReadColumns = Book' (Column PGText) (Column PGText) (Column (Nullable PGText)) 

type BookWriteColumns = Book' (Column PGText) (Column PGText) (Maybe (Column (Nullable PGText))) 

type BookNullableColumns = Book' (Column (Nullable PGText)) (Column (Nullable PGText)) (Column (Nullable PGText)) 

The result of a query returning BookReadColumns is Book, however there is no type synonym for the value of a query returning BookNullableColumns.

It would be great to also have this synonym and function.

type BookMaybe = Book' (Maybe Text) (Maybe Text) (Maybe Text)

sequenceBook :: BookMaybe -> Maybe Book
sequenceBook (Book c1 c2 c3) = Book <$> c1 <*> c2 <*> pure c3

This would certainly be useful when Book is on the right of a left join.

@tomjaguarpaw: Is this a sensible way of dealing with products of nullable columns? As far as I can tell the machinery in product-profunctors isn't quite adequate due to the fact that Nothing is a valid value for bookBlurb and the conversion from BookMaybe shouldn't return Nothing because of that.

tomjaguarpaw commented 8 years ago

On Sun, Nov 13, 2016 at 12:58:30PM -0800, Joe Hermaszewski wrote:

type BookMaybe = Book' (Maybe Text) (Maybe Text) (Maybe Text)

sequenceBook :: BookMaybe -> Maybe Book
sequenceBook (Book c1 c2 c3) = Book <$> c1 <*> c2 <*> pure c3

This would certainly be useful when Book is on the right of a left join.

cc @tomjaguarpaw: Is this a sensible way of dealing with products of nullable columns? As far as I can tell the machinery in product-profunctors isn't quite adequate due to the fact that Nothing is a valid value for bookBlurb and the conversion from BookMaybe shouldn't return Nothing because of that.

Hi Joe, could you explain more about what you mean about products of nullable columns? I don't quite understand.

expipiplus1 commented 8 years ago

@tomjaguarpaw The type synonym BookNullableColumns in the first post is an example of this.

A use case might be in performing a left join between tables A and B. It seems quite handy to be able to end up with [(ThingA, Maybe ThingB)] after running the query. The query itself could return [(ThingA, ThingBMaybe)] and sequenceThingB seems to fill the gap here.

tomjaguarpaw commented 8 years ago

Have you seen the new FunctionalJoin module? It's a more sane approach to outer joins.

https://www.stackage.org/haddock/nightly-2016-11-09/opaleye-0.5.2.1/Opaleye-FunctionalJoin.html

expipiplus1 commented 8 years ago

Yeah, I was trying to coerce that into doing what I want earlier today. It's certainly an improvement on leftJoin. Please correct me if I'm wrong, but I don't think it's possible to have this (or something equivalent) columnsResult ~ (columnsL, Maybe columnsR).

tomjaguarpaw commented 8 years ago

It's not possible at all in Opaleye, for good reason. The constructor of the Maybe must be statically known at Query run time so can't possibly depend on the inputs to the left join.

expipiplus1 commented 8 years ago

I thought that might be the case. Looking forward to the monadic interface ;-)

tomjaguarpaw commented 8 years ago

It's not possible for any interface where Query is a functor, otherwises you could map over the constructor of the Maybe and get a query result that depends on a Haskell function.

expipiplus1 commented 8 years ago

;-)

tomjaguarpaw commented 8 years ago

;-)

Oh, ;-)

tomjaguarpaw commented 8 years ago

Returning to this:

BookMaybe -> Maybe Book

data P a b = P (a -> Maybe b)

is an instance of ProductProfunctor, and then

instance Default P (Maybe a) a where
    def = id

gives you sequenceBook = def.

tomjaguarpaw commented 8 years ago

(which, of course, generalises to all Applicatives)

tomjaguarpaw commented 8 years ago

You may also want

instance Default P (Maybe a) (Maybe a) where
    def = pure

depending on your application.

expipiplus1 commented 8 years ago

@tomjaguarpaw I assume you mean

instance Default P (Maybe a) a where
  def = P id

instance Default P (Maybe a) (Maybe a) where
  def = P pure

sequenceJobset :: JobsetMaybe -> Maybe Jobset
sequenceJobset = unP def

I neglected Default when looking for a product-profunctors solution to this. It's certainly nicer than expanding to long applications of <*>.

There's probably still a case to be made for the sequence* functions being around as the type inference around leftJoin and def is (understandably) terrible :)

expipiplus1 commented 8 years ago

Also sequence perhaps isn't the best name for this, suggestions welcome.

tomjaguarpaw commented 8 years ago

Indeed, that's what I mean.

If you want the function for it's type signature I guess that's fine but you may as well use product-profunctors for the function body.

folsen commented 8 years ago

@expipiplus1 Reviewing this now, I feel like this is a useful addition, so would like to merge it. Do you want to change the function body implementation per Toms feedback, perhaps that would further reduce clutter a little bit?

I also intend to (at some point) add another flag to allow enabling/disabling generating the NullableColumn stuff, it's not always relevant so could include this there as well then.

As for naming, since we have BookReadColumns associated with Book perhaps a more natural dual to BookNullableColumns is NullableBook instead of BookMaybe?

Then you could call the function that turns a NullableBook into a Maybe Book something like fromNullableBook, maybeBook or something more "natural" like that?

If you want to make any of these changes, please feel free, if not I will merge anyway and let someone else with a stronger opinion than me change it to what they want :)

expipiplus1 commented 8 years ago

@folsen all those suggestions sounds good, I'll see about getting them done today.

Would BookNullable be ok?

I'm not totally sure about the name NullableBook as it would be nice to keep Book as a prefix. On the other hand it'd be nice to keep fromNullable as a prefix to all the conversion functions.

folsen commented 8 years ago

Yeah, all the other type synoms start with Book I suppose so it would make sense that that one would as well. I have no strong opinion either way, so do what you feel is best!

tomjaguarpaw commented 8 years ago

I also intend to (at some point) add another flag to allow enabling/disabling generating the NullableColumn stuff

By the way, I'm really hoping to move away from NullableColumn stuff now that FunctionalJoin exists.