Is there a way to define aliases for partially applied data types

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.