Selda, type operators and heterogeneous lists

Hi there! I'm using [selda](https://github.com/valderman/selda) package to deal with relational databases. This package represents database tables in haskell in a type-safe way. And to do so it leans on type operators. For example, here they are two different tables: ``` categories :: Table (RowID:*:Text) expenses :: Table (RowID:*:Text:*:Double:*:RowID) ``` As both of them belong to different types, I can't put them in a single list out of the box: ``` [categories, expenses] --- not valid!! ``` I'm aware of [different solutions](https://wiki.haskell.org/Heterogenous_collections) to deal with it, but they don't convince me: - Algebraic data types: I think in this situation it is specially a burden having to play type-switching... There can exist a lot of different table schemas and they may change at any moment. - Universal type: I see it as a kind of hack - Existential types: I have read that usually it is an anti-pattern, and in this case I don't understand how it should work, because types are constructed with `:*:` operator. So my question if it exists some natural way to treat types built with type operators as a single type. I have to recognize I don't understand well how all this stuff about type operators works. I have read something but I still don't get it. So any direction with it would be very gratifying, too :) Thanks in advance. Marc Busqué http://waiting-for-dev.github.io/about/

On Fri, Apr 13, 2018 at 03:59:44PM +0200, Marc Busqué wrote:
``` categories :: Table (RowID:*:Text) expenses :: Table (RowID:*:Text:*:Double:*:RowID) ```
As both of them belong to different types, I can't put them in a single list out of the box:
Before we can help we need to know more. Specifically, why do you want to put them in a single list?

On Fri, 13 Apr 2018, Tom Ellis wrote:
On Fri, Apr 13, 2018 at 03:59:44PM +0200, Marc Busqué wrote:
Before we can help we need to know more. Specifically, why do you want to put them in a single list?
I want to make the same action with more than one (creating them in the database server): ``` migrate :: IO () migrate = do dir <- dBDir createDirectoryIfMissing True dir forM_ [categories, expenses] $ withDB . createTable ``` Marc Busqué http://waiting-for-dev.github.io/about/

Am 13.04.2018 um 16:38 schrieb Marc Busqué:
On Fri, 13 Apr 2018, Tom Ellis wrote:
On Fri, Apr 13, 2018 at 03:59:44PM +0200, Marc Busqué wrote:
Before we can help we need to know more. Specifically, why do you want to put them in a single list?
I want to make the same action with more than one (creating them in the database server):
``` migrate :: IO () migrate = do dir <- dBDir createDirectoryIfMissing True dir forM_ [categories, expenses] $ withDB . createTable ```
So a possible solution is to store a list of actions instead of a list of items (untested code): let dbCreateTable = withDB . createTable sequence_ $ map dbCreateTable categories ++ map dbCreateTable expenses or perhaps, if the types allow it: withDB $ do let dbCreateTable = withDB . createTable sequence_ $ map createTable categories ++ map createTable expenses

On Fri, Apr 13, 2018 at 04:38:46PM +0200, Marc Busqué wrote:
On Fri, 13 Apr 2018, Tom Ellis wrote:
On Fri, Apr 13, 2018 at 03:59:44PM +0200, Marc Busqué wrote:
Before we can help we need to know more. Specifically, why do you want to put them in a single list?
I want to make the same action with more than one (creating them in the database server):
``` migrate :: IO () migrate = do dir <- dBDir createDirectoryIfMissing True dir forM_ [categories, expenses] $ withDB . createTable ```
I think I would just tolerate forM_ [createTable categories, createTable expenses] $ withDB

Thanks for both answers. It wasn't what I had in mind, but surely it is just that I have to get used to Haskell strong typing. Until now I think I'm used to apply DRY beyond types :) So, from you answers, I can conclude that there is no way to tell something like the following in a type signature: "any type build with that type operators". Isn't it?

I would argue that in this case existential types actually are the correct
tool. What you want to do is hide some amount of type information, which is
exactly what existential types do. Then, because createTable can handle any
Table a when you unwrap the Table from the existential type you can still
pass it to createTable.
Here's a sort of mock example:
```{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE LambdaCase #-}
import Control.Monad (forM_)
data Table a
data a :*: b where
(:*:) :: a -> b -> a :*: b
infixr 1 :*:
type RowID = Int
type Text = String
categories :: Table (RowID :*: Text)
categories = undefined
expenses :: Table (RowID:*:Text:*:Double:*:RowID)
expenses = undefined
createTable :: Table a -> IO ()
createTable _ = return ()
data ExTable = forall a. ExTable (Table a)
main :: IO ()
main = forM_ [ExTable categories, ExTable expenses] (\case ExTable t ->
createTable t)
```
In your example this requires more boilerplate and doesn't seem much better
than [createTable categories, createTable expenses], but this provides a
way to actually have a list of tables of differing types without applying
createTable to them first and I think that's closer to what you were going
for.
On Sun, Apr 15, 2018 at 12:35 PM Marc Busqué
Thanks for both answers. It wasn't what I had in mind, but surely it is just that I have to get used to Haskell strong typing. Until now I think I'm used to apply DRY beyond types :)
So, from you answers, I can conclude that there is no way to tell something like the following in a type signature: "any type build with that type operators". Isn't it? _______________________________________________ Haskell-Cafe mailing list To (un)subscribe, modify options or view archives go to: http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe Only members subscribed via the mailman list are allowed to post.

I would argue that in this case existential types actually are the correct tool. What you want to do is hide some amount of type information, which is exactly what existential types do. Then, because createTable can handle any Table a when you unwrap the Table from the existential type you can still pass it to createTable.
Thanks Jake for your response. I think it makes a lot of sense. I'll give it a try. Marc Busqué http://waiting-for-dev.github.io/about/
participants (4)
-
Ben Franksen
-
Jake
-
Marc Busqué
-
Tom Ellis