New_Caird
New_Caird

Reputation: 49

Writing an Instance of Functor for a Type-Synonym and Being Confused

I'm trying to implement a generic ring-like thing and apply it to a music data structure described by Paul Hudak in the book The Haskell School of Music. Naturally there's lots of Semigroup/Monoid shenanigans that are omitted, but I have the following relevant code:

newtype Duo     a b     = Duo     {duo1     :: a} -- A ring-like structure

data Song a =
       Primitive a
     | Song a :+: Song a             -- Composing Music Sequentially
     | Song a :=: Song a deriving Eq -- Composing Music Concurrently (in parallel)

instance Functor Song where
     fmap f (x :+: y)     = fmap f x :+: fmap f y
     fmap f (x :=: y)     = fmap f x :=: fmap f y
     fmap f (Primitive x) = Primitive $ f x

newtype Concurrent a = Concurrent {fromConcurrent :: Song a} deriving (Show)
newtype Sequential a = Sequential {fromSequential :: Song a} deriving (Show)

type Music a = Duo (Maybe (Concurrent a)) (Maybe (Sequential a))

I'm attempting to write an instance of Functor for Music, as Duo doesn't have a Functor I thought this wouldn't be a problem.

I wrote the following implementation:

instance Functor Music where
     fmap :: (a -> b) -> Music a -> Music b
     fmap f = Duo . fmap (fmap f . fromConcurrent) . duo1

But I get the following error:

• The type synonym ‘Music’ should have 1 argument, but has been given none
• In the instance declaration for ‘Functor Music’
| 167 | instance Functor Music where
|          ^^^^^^^^^^^^^

Perhaps the problem is just that I'm essentially writing a Functor for only a Subset of Duo, maybe this can only work if I make music a newtype instead of just a type synonym. I'm really hoping to avoid that on account of the already egregious number of wrappers going on in this code, I'd really hate to add another. Maybe you all can think of a way to implement a functor for Duo that makes sense, and makes this all work?

One thing I really don't understand is why it let's me make an instance for show:

instance (Show a) => Show (Music a) where
     show (Duo Nothing)               = "Silence"
     show (Duo (Just (Concurrent x))) = show x

Upvotes: 2

Views: 91

Answers (1)

Daniel Wagner
Daniel Wagner

Reputation: 152837

Unfortunately, this kind of type-level abstraction is not allowed in Haskell. But I think you might have made some sort of conceptual error in your data type design somewhere. Expanding the types, since Duo has only one field, we would have:

Music a ~= Duo (Maybe (Concurrent a)) (Maybe (Sequential a))
        ~= Maybe (Concurrent a)
        ~= Maybe (Song a)

Is that really what you intended?

In the unlikely event that it is, I think I'd be very tempted to cut out the middlemen.

data Music a = Silence | Sound (Song a)
instance Functor Music where
    fmap f Silence = Silence
    fmap f (Sound notes) = Sound (fmap f notes)

If this is the intended behavior but you must keep the middlemen, newtype is the way forward.

In the likely event that this was not how you intended Music to behave, we can probably give you more/better advice, but we'd need to know more about what you intend Duo to mean.

Regarding your edit/comment about Show: in your Show instance, you include a type argument to Music. The problem with the Functor instance is exactly the fact that you do not include the argument. In the Show instance, because the argument is available, the compiler can simply expand the type alias, so the line

instance Show a => Show (Music a)

is identical in every way the compiler cares about to the line

instance Show a => Show (Duo (Maybe (Concurrent a)) (Maybe (Sequential a)))

In your Functor instance, though, the compiler cannot expand the type alias, because you have not supplied the type argument. You could imagine adding type-level lambdas to Haskell, so that you could have instance Functor Music mean something like

instance Functor (\a -> Duo (Maybe (Concurrent a)) (Maybe (Sequential a)))

but that has some serious problems. The naive way of adding it leads you to need to do higher-order unification during type-checking, which is undecidable.

Upvotes: 5

Related Questions