I have a data type NodeF atom a
. I’d like to make two flavors of the this data type called CharNodeF a
and TokenNodeF a
. These would be equivalent to NodeF Char
and NodeF String
. Is there anyway to do that? Right now I’m writing out (NodeF Char)
wherever I need a CharNodeF
. Here’s some of the code:
data NodeF atom a
= Fraction { id :: Int, numerator :: Array a, denominator :: Array a }
| Atom { id :: Int, value :: atom }
derive instance charNodeFunctor :: Functor (NodeF Char)
instance showCharNodeF :: Show a => Show (NodeF Char a) where
show (Fraction {numerator, denominator}) = "(num:" <> (show numerator) <> ", den:" <> (show denominator) <> ")"
show (Atom {value, id}) = (show value) <> ":" <> (show id)
Ideally, you would use type aliases:
type CharNodeF a = NodeF Char a
type TokenNodeF a = NodeF String a
However, you cannot define type class instances for type aliases. This is a restriction the core contributors to the PS language want to drop in the future, but it hasn’t happened yet. Since you already have a Functor
instance for NodeF Char
, you’ll have to use newtypes:
newtype CharNodeF a = CharNodeF (NodeF Char a)
instance showCharNodeF :: Show a => Show (CharNodeF a) where
show (CharNodeF nodeFCharA) = case nodeFCharA of
Fraction {numerator, denominator} -> "(num:" <> (show numerator) <> ", den:" <> (show denominator) <> ")"
Atom {value, id} -> (show value) <> ":" <> (show id)
However, then you’ll deal with the newtype constructor wrapping/unwrapping boilerplate.
So, here’s the possible decisions you can make:
- choose to write ‘NodeF Char’ in type signatures a lot
- choose to write
un CharNodeF charNodeFValue
and/or (CharNodeF nodeFCharA)
in value-level terms / pattern matching a lot
- use type aliases and code without using type classes
2 Likes
Thank you for the reply. I think I stick with NodeF Char
for now. Looking forward to PS dropping that restriction on type aliases in the future.
This is a restriction the core contributors to the PS language want to drop in the future, but it hasn’t happened yet.
Is there a GitHub ticket I could upvote for this?
I don’t think it a good idea, automatically generating instances by something like deriving via
could be better.
By “dropping the restriction”, it would just be expanding the synonym. Synonyms are never nominal and will not ever affect instance dispatch.
1 Like
Here’s a concrete example of what Nate was talking about (or is at least my understanding of how it would work once implemented and merged):
data Original atom = Box String
type IndentBy5 = Original Five
type IndentBy2 = Original Two
instance showIndentBy0 :: Show IndentBy0 where
show (Box s) = s
instance showIdentityBy2 :: Show IndentBy2 where
show (Box s) = " " <> s
-- which "dealiases" / "expands the synonym" to
instance showIndentBy0 :: Show (Original Zero) where
show (Box s) = s
instance showIndentBy2 :: Show (Original Two) where
show (Box s) = " " <> s
-- Since the desugared types (Original Zero / Original Two)
-- are still different, these are valid instances,
-- just a lot easier to read and write.
type Same1 = Original One
type Same2 = Original One
instance showSame1 :: Show Same1 where
show (Box s) = "a" <> s
instance showSame2 :: Show Same2 where
show (Box s) = "b" <> s
-- which "dealiases" / "expands the synonym" to
instance showSame1 :: Show (Original One) where
show (Box s) = "a" <> s
instance showSame2 :: Show (Original One) where
show (Box s) = "b" <> s
-- Since the desugared types (Original One / Original One)
-- are the same, these are invalid instances and
-- will result in a compiler error.
well, sounds good. Currently it’s annoying to make instance for record types.