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

#1

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)
#2

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
#3

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?

#4

I don’t think it a good idea, automatically generating instances by something like deriving via could be better.

#5

By “dropping the restriction”, it would just be expanding the synonym. Synonyms are never nominal and will not ever affect instance dispatch.

1 Like
#6

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.
#7

well, sounds good. Currently it’s annoying to make instance for record types.