2

Here is my problem :

Let's say I have various kinds of objects to handle but they share the same form : We have Items that are made of a String (say an id) and of a Content which can be anything. So the broken-down problem can be summarized as follow : I'd like to be able to produce an item from a content associating it an id in a generic manner but I'd like the type-system to constrain the interface such that I know I'll get back the an Item of the passed content.

Here is an example of an attempt using FunctionalDependencies :

{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} main :: IO () main = do putStrLn $ show $ handleContent TitiAssociation $ read "TitiContent" putStrLn $ show $ handleContent TotoAssociation $ read "TotoContent" -- Expected -- "TitiItem" -- "TotoItem" -- definition of the domain data TitiContent = TitiContent String deriving (Read, Show) data TotoContent = TotoContent String deriving (Read, Show) data TitiItem = TitiItem String TitiContent deriving (Read, Show) data TotoItem = TotoItem String TotoContent deriving (Read, Show) -- class (Read a, Show a) => Content a where class (Read a, Show a) => Item a where instance Content TitiContent where instance Content TotoContent where instance Item TitiItem where instance Item TotoItem where -- Association of types class (Content contentType, Item itemType) => ItemContentAssociation association contentType itemType | association -> contentType, association -> itemType, itemType -> association where -- tokens to identify the types which will be handled data TitiAssociation = TitiAssociation data TotoAssociation = TotoAssociation instance ItemContentAssociation TitiAssociation TitiContent TitiItem where instance ItemContentAssociation TotoAssociation TotoContent TotoItem where -- generic function for handling handleContent :: (ItemContentAssociation association contentType itemType) => association -> contentType -> itemType handleContent TitiAssociation TitiContent = TitiItem handleContent TotoAssociation TotoContent = TotoItem 

but then the compiler complains :

tmp.hs:41:15: error: * Couldn't match expected type `ass' with actual type `TitiAss' `ass' is a rigid type variable bound by the type signature for: handleContent :: forall ass contentType itemType. ItemContentAss ass contentType itemType => ass -> contentType -> itemType at tmp.hs:40:1-92 * In the pattern: TitiAss In an equation for `handleContent': handleContent TitiAss TitiContent = TitiItem * Relevant bindings include handleContent :: ass -> contentType -> itemType (bound at tmp.hs:41:1) 

I've also tried various flavours of the TypeFamilies extension but the compiler always complains (could provide more example if required but intended to keep the initial post of a reasonable size at first).

I'm quite new in the world of functionanl programming so do not hesitate to bring up new approaches as I'm sure I'm overlooking many aspects of the problem.

Thanks a lot in advance, Cheers !

2
  • Make handleContent a method of ItemContentAssociation. Commented Aug 11, 2021 at 19:26
  • @DanielWagner it actually could indeed be the solution... Hitting on the problem for a couple of days. Visibly cannot see straight anymore... ^^' I'll try to get it to work on the real-world example and clarify the question if that's not the case. Any idea of how to do it with TypeFamilies (especially 'associated types') ? I find it kind of more readable and they overlap quite a bit in term of functionality from what I get. Commented Aug 11, 2021 at 19:48

1 Answer 1

2

Almost certainly the right answer, in both the MPTCs/FunDeps world and in the TF world, is to make handleContent a method of ItemContentAssociation. Here's specifically what that would look like with type families, since you ask about that in the comments.

{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilyDependencies #-} main :: IO () main = do putStrLn $ show $ handleContent TitiAssociation $ read "TitiContent \"titi\"" putStrLn $ show $ handleContent TotoAssociation $ read "TotoContent \"toto\"" -- Expected -- "TitiItem" -- "TotoItem" -- definition of the domain data TitiContent = TitiContent String deriving (Read, Show) data TotoContent = TotoContent String deriving (Read, Show) data TitiItem = TitiItem String TitiContent deriving (Read, Show) data TotoItem = TotoItem String TotoContent deriving (Read, Show) -- class (Read a, Show a) => Content a where class (Read a, Show a) => Item a where instance Content TitiContent where instance Content TotoContent where instance Item TitiItem where instance Item TotoItem where -- Association of types class (Content (ContentType association), Item (ItemType association)) => ItemContentAssociation association where type ContentType association = content | content -> association type ItemType association handleContent :: association -> ContentType association -> ItemType association -- tokens to identify the types which will be handled data TitiAssociation = TitiAssociation data TotoAssociation = TotoAssociation instance ItemContentAssociation TitiAssociation where type ContentType TitiAssociation = TitiContent type ItemType TitiAssociation = TitiItem handleContent TitiAssociation c@(TitiContent s) = TitiItem s {- what string should be used here? if s, why also have c? -} c instance ItemContentAssociation TotoAssociation where type ContentType TotoAssociation = TotoContent type ItemType TotoAssociation = TotoItem handleContent TotoAssociation c@(TotoContent s) = TotoItem s {- what string? -} c 

That said, something smells very wrong here. The amount of duplicated code makes me suspect you're bringing a wrong idea about how to set things up from your other favorite language(s). It's hard to say more about how to fix it without learning more about what motivated these definitions, though.

Sign up to request clarification or add additional context in comments.

6 Comments

Awesome ! And yeah, well-spotted. I habve a professional Java background but much interested in Haskell (pure functional programming).
So basically what I am trying to achieve is a generic CRUD server. Basically I'd like to have in separated modules (such as libraries) the core logic (i.e. posting content giving back an item by just adding an ID to it) and the various domains (i.e. notes whose content are title and body) or checklists (whose content is a list of labels) But then the core logic shouldn't care about the types it handles since it just adds ID (say hash of serialization).
@amaille Why isn't a single parameterized data type (and no classes or associated types), like data Identified a = Identified { identifier :: UUID, payload :: a }, good enough for that?
Actually, you got me to take a step back. The more I think to the problem, the more I think that I don't put the right responsibilities where they should be... Gotta think more about it. Accepting your answer though because it might not be idiomatic but its beautiful :)
Identified a could definitely be sufficient but then I cannot constrain the a type on the client side to be from the domain in the case of an isomorphic application (kinda guessing here) @DanielWagner
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.