Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Heterogeneous types as values of Data.Map

Tags:

haskell

map

I would like to be able to do something like this, which is clearly not permitted.

import qualified Data.Map as Map
x = Map.fromList [ 
      ("Name",    ["John", "Steve", "Kelly", "Zoe"]), 
      ("Surname", ["Smith", "Lee", "Ng", "White"]), 
      ("Age",     [1, 2, 3, 4]) 
    ]

One possible solution is to create a type like this:

data L = LInt [Int] | LString [String]

x = Map.fromList [ 
      ("Name",    LString ["John", "Steve", "Kelly", "Zoe"]),
      ("Surname", LString ["Smith", "Lee", "Ng", "White"]), 
      ("Age",     LInt    [1, 2, 3, 4]) 
    ]

which is undesirable. Is there are a better way?

My actual use case: Very broadly, I am trying to implement parts of dplyr (http://cran.r-project.org/web/packages/dplyr/vignettes/introduction.html), which can be viewed as a "grammar of data". Dplyr has expressions for column and row selection of tabular data as well as grouping and joining of rows, where the number of rows can be in the millions and the number of columns in the hundreds. It's not the data size that concerns me now, but the ability to produce capabilities and workflow similar to that package

like image 780
gappy Avatar asked Oct 20 '22 21:10

gappy


1 Answers

You can use existential quantification for this

{-# LANGUAGE ExistentialQuantification, FlexibleInstances, TypeSynonymInstances #-}

import qualified Data.Map as Map

class ColumnData c
instance ColumnData Integer
instance ColumnData String

data Column = forall c . ColumnData c => Column [c]

x = Map.fromList [
        ("Name",    Column ["John", "Steve", "Kelly", "Zoe"]),
        ("Surname", Column ["Smith", "Lee", "Ng", "White"]),
        ("Age",     Column [1::Integer, 2, 3, 4])
    ]

(Note we need an explicit type annotation here in the last column, because literals are overloaded, and existentials cannot quite cope with that).

Now of course the real type of the mapped data is not available from x. You only have forall a . a => Column a, and the only thing you can do with it is to call a method of class Column. This example defines none. You should add your own methods, which you must choose wisely.

Note, this technique is frowned upon by many hardcore haskellers, for reasons that I don't quite understand. It would be great if someone could shed some light on this.

like image 164
n. 1.8e9-where's-my-share m. Avatar answered Nov 15 '22 09:11

n. 1.8e9-where's-my-share m.