Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedded sum types #5

Open
lykahb opened this issue May 10, 2013 · 3 comments
Open

Embedded sum types #5

lykahb opened this issue May 10, 2013 · 3 comments

Comments

@lykahb
Copy link
Owner

lykahb commented May 10, 2013

Implementing embedded sum types will allow more flexible mapping between types and DB. Field types like (Either Int String) or (Maybe MyEmbedded) where Maybe is treated as another embedded sum type will become possible.

We have to specify precise behavior for the following operations before starting implementation. The main obstacle for the feature is semantics of accessing subfields that have a sum type in their chain. Such subfields can appear in projections, conditions, updates, etc.

Mapping embedded sum datatype to columns:

The discriminator column and nullable columns for the fields from all constructors.

Conversion to PersistValues:

toPersistValues returns: discriminator value, NULLs for the columns in the other constructors. Note that since we cannot call toPersistValues for values in the other constructors, we must know the number of columns to pad values with nulls. To get this number we can either analyze dbType (slow) or create a new PersistField function numberOfColumns.

Creating data values:

fromPersistValues chooses constructor basing on the discriminator value, skips the NULLs for the other constructors using number of columns for their field types.

Example:

data Sum = One Int | Two String | Three (Int, String)
columns: sumDiscr INTEGER NOT NULL, one INTEGER, two VARCHAR, three#val0 INTEGER, three#val1 VARCHAR
toPersistValues (Two "abc") -> [PersistInt64 1, PersistNull, PersistString "abc", PersistNull, PersistNull]

Subfields:

Consider
data MyEntity = MyEntity {myEither :: Either Int Sum}
project (MyEither ~> LeftSelector)
project (MyEither ~> RightSelector). In this case all values of Sum including its discriminator may be NULLs.

Since any column of a embedded sum type can be NULL, the final type of selector chains must have Maybe (or another optional type), eg, SubField v c (Maybe a). Example:
MyEither ~> LeftSelector :: SubField MyEntity MyEntityConstructor (Maybe Int)
MyEither ~> RightSelector :: SubField MyEntity MyEntityConstructor (Maybe Sum)
MyEither ~> RightSelector ~> TwoSelector :: SubField MyEntity MyEntityConstructor (Maybe String)

instance Embedded (Either a b) where
data Selector (Either a b) c where
LeftSelector :: Selector v c (Maybe a)
RightSelector :: Selector v c (Maybe b)
selectorNum LeftSelector = 0
selectorNum RightSelector = 1

But in this case to access nested sum types, operator ~> should be applicable both to (emb :: f db r a) and (emb :: f db r (Maybe a)) which requires some class hackery. Also we will have nested Maybes and it is not clear how a single PersistField (Maybe a) instance can handle them.

If the fields we select are nullable themselves, we should distinguish between nulls in case when our type has different constructor and when constructor matches, but field is null. Either Int (Maybe Int). To do this we may select the discriminator column for the innermost embedded type. But in this case the number of columns in comparison expressions will not match.

Another approach for projection is to use another subfield type for a chain that contains a sum type. A separate Projection instance will add Maybe. Something like (Projection (SumTypeSubField a) db r (Maybe a)).

@mightybyte
Copy link
Contributor

+1 for this. I was looking at using groundhog the other day and embedded sum types were exactly what I needed.

@wraithm
Copy link
Contributor

wraithm commented Oct 26, 2014

Yeah! This would be great :)

@lykahb
Copy link
Owner Author

lykahb commented Nov 1, 2014

I think it would be a useful feature but it has many cases where behavior is undefined. For example when we try to access a value inside of an embedded type created with a different constructor:

k <- insert $ MyEntity $ Right (One 1)
update [MyEither ~> LeftSelector =. 1] $ AutoKeyField ==. k

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants