-
Notifications
You must be signed in to change notification settings - Fork 68
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
TypeFamilies vs MultiParamTypeClasses #3
Comments
There are good discussions on TF vs MPTC+FD in the "Associated Type Synonyms" paper: https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/at-syns.pdf and here: https://ghc.haskell.org/trac/ghc/wiki/TFvsFD However, most arguments (e.g. expressivity) appear to be non-applicable to our simple case. |
@ndmitchell has written a draft implementation with There is no functional dependency |
Note that |
Ah, I see! So it is possible to have a higher-kinded Thanks! This is an interesting design point I haven't considered. |
You don't really lose any instances since you can have a new type with a phantom parameter. You certainly make those ones a bit harder to use though - but they are pretty rare so no big deal. |
Good point. So, let's summarise the pros and cons of Some examples of type signatures: ab :: (Graph g, Vertex g ~ Char) => g
ab :: Graph g Char => g Char
ab = connect (vertex 'a') (vertex 'b')
overlays :: Graph g => [g] -> g
overlays :: Graph g a => [g a] -> g a
edges :: Graph g => [(Vertex g, Vertex g)] -> g
edges :: Graph g a => [(a, a)] -> g a
path :: Graph g => [Vertex g] -> g
path :: Graph g a => [a] -> g a
gmap :: Graph g => (a -> Vertex g) -> GraphFunctor a -> g
gmap :: Graph g b => (a -> b) -> GraphFunctor a -> g b
box :: (Graph g, Vertex g ~ (u, v)) => GraphFunctor u -> GraphFunctor v -> g
box :: Graph g (u, v) => GraphFunctor u -> GraphFunctor v -> g (u, v) I'd say there is no clear winner here. Dealing with non-fully parametric instances may require workarounds with Any other points to consider? |
MPTC is a much older, simpler and more common extension than TF. I suspect you are less likely to have type inference problems in corner cases, especially when composing things. I find the MPTC much simpler to read than TF. Yes, Vertex isn't explicit, but as soon as you define more complex operations it is easier to keep track of. As examples of things mentioning more than one graph/vertex type: replaceGraph :: (Graph g1, Graph g2, Vertex g1 ~ a, Vertex g2 ~ a) => g1 -> g2
replaceVertex :: (Graph g1, Graph g2, Vertex g1 ~ a, Vertex g2 ~ b) => g1 -> g2
replaceGraph :: (Graph g1 a, Graph g2 a) => g1 a -> g2 a
replaceVertex :: (Graph g a, Graph g b) => g a -> g b What issues were you expecting with non-fully parametric instances? |
Indeed, these are good examples where
Well, as discussed above, you can't make P.S.: Just to show why I care about strange things like -- Below ~~ stands for the equality up to an isomorphism, e.g. (x, ()) ~~ x
box x y ~~ box y x
box x (box y z) ~~ box (box x y) z
box x () ~~ x
box x empty ~~ empty -- i.e. empty is annihilating zero for the box operation! |
Hmm, the following {-# LANGUAGE ConstraintKinds #-}
type Graphable g a = (Graph g, Vertex g ~ a)
edges :: Graphable g a => [(a, a)] -> g Here With this approach we can have both We can define |
You still don't get MPTC like type signatures - you get a simpler context in the examples above, but the signature itself is still clearer with MPTC (the bit changing is clearer). For things like |
@ndmitchell Have a look at #9 -- higher-kinded Furthermore, I just realised that we can make |
I think the solution we currently have is optimal: Users who don't care about type classes can happily use |
The current implementation defines the
Graph
type class using an associated type family:An alternative is to use
MultiParamTypeClasses
andFunctionalDependencies
:My current preference is the type families approach, but @ndmitchell suggests to give MPTC a serious consideration. What are the pros and cons?
Two example type signatures:
I think I prefer type families in both cases, although MPTC are a little terser. Are there cases where MPTC is clearly much better? Is there any difference in type inference and error messages?
Note: in this issue we do not discuss whether
Graph
should be higher-kinded. This is an orthogonal design question, deserving a separate issue.The text was updated successfully, but these errors were encountered: