1

I'm trying to create a class Func which represents a function, and then a data type Dot which composes functions. Below is my attempt, but I'm getting compile errors:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE GADTs #-}

module Func where
  class Func f a b | f -> a, f -> b where
    apply :: f -> a -> b

  data Dot f1 f2 where
    Dot :: (Func f1 a b, Func f2 b c) => f1 -> f2 -> Dot f1 f2

  instance Func (Dot f1 f2) a c where
    apply (Dot f1 f2) = (apply f2) . (apply f1)

I get the following errors:

Func.hs:15:26:
    Could not deduce (Func f2 b c) arising from a use of `apply'
    from the context (Func f1 a1 b, Func f2 b c1)
      bound by a pattern with constructor
                 Dot :: forall f1 f2 a b c.
                        (Func f1 a b, Func f2 b c) =>
                        f1 -> f2 -> Dot f1 f2,
               in an equation for `apply'
      at Func.hs:15:12-20
    Possible fix:
      add (Func f2 b c) to the context of
        the data constructor `Dot'
        or the instance declaration
    In the first argument of `(.)', namely `(apply f2)'
    In the expression: (apply f2) . (apply f1)
    In an equation for `apply':
        apply (Dot f1 f2) = (apply f2) . (apply f1)

Func.hs:15:39:
    Could not deduce (Func f1 a b) arising from a use of `apply'
    from the context (Func f1 a1 b, Func f2 b c1)
      bound by a pattern with constructor
                 Dot :: forall f1 f2 a b c.
                        (Func f1 a b, Func f2 b c) =>
                        f1 -> f2 -> Dot f1 f2,
               in an equation for `apply'
      at Func.hs:15:12-20
    Possible fix:
      add (Func f1 a b) to the context of
        the data constructor `Dot'
        or the instance declaration
    In the second argument of `(.)', namely `(apply f1)'
    In the expression: (apply f2) . (apply f1)
    In an equation for `apply':
        apply (Dot f1 f2) = (apply f2) . (apply f1)

What do I need to fix to make this compile?

I realise this might seem a bit silly, but I thought it might be useful to be able to carry around different functions as unique types, as one could perhaps attach various metadata to those types (I'm not sure exactly what yet).

Clinton
  • 22,361
  • 15
  • 67
  • 163

2 Answers2

5

The problem is that the instance head of Func (Dot f1 f2) a c can't "look into" f1 and f2 to deduce the functional dependencies. You need to do this yourself, which is much easier with the equivalent formulation

{-# LANGUAGE TypeFamilies      #-}

class Func f where
  type FuncArg f :: *
  type FuncRes f :: *
  apply :: f -> FuncArg f -> FuncRes f

data Dot f1 f2 where
  Dot :: (Func f1, Func f2, FuncArg f2~FuncRes f1)
                                 => f1 -> f2 -> Dot f1 f2

instance Func (Dot f1 f2) where
  type FuncArg (Dot f1 f2) = FuncArg f1
  type FuncRes (Dot f1 f2) = FuncRes f2
  apply (Dot f1 f2) = (apply f2) . (apply f1)

Or do you have any particular reason to prefer fundeps over type families?

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • No reason to prefer fundeps over type families, I'm just learning, so if type families is a better way, I'll try that. – Clinton Jan 05 '13 at 14:43
  • @Clinton Type families are not a better way in all situations, but here (and in many other situations), I'd say they are preferable. – Daniel Fischer Jan 05 '13 at 14:44
  • Daniel: When are they not preferable? leftaroundabout's suggestion looks quite clean to me. It's a little but more verbose, that's all. – Clinton Jan 05 '13 at 14:46
  • @Clinton: http://www.haskell.org/haskellwiki/Functional_dependencies_vs._type_families – leftaroundabout Jan 05 '13 at 14:49
  • @Clinton I don't remember an example off-hand, but there are some things that (currently, at least) can better be expressed with FunDeps than with type families. At the moment, type families don't work with `OverlappingInstances`, while `FunctionalDependencies` does, so if you need overlapping instances, you haven't the choice. – Daniel Fischer Jan 05 '13 at 14:49
  • There's nothing in the above formulation that restricts `Dot` to only `Func`s with a result where `f1` and `f2` match, and hence I'm getting compile errors. Is there some extra constraints I need to add to `Dot`? – Clinton Jan 05 '13 at 14:54
  • 1
    Worked out the last comment, adding: `FuncRes f1 ~ FuncArg f2` to the context of `Dot` does the trick. – Clinton Jan 05 '13 at 14:56
  • Of course, I should have mentioned. That's just the direct translation of `(Func f1 a b, Func f2 b c)`. – leftaroundabout Jan 05 '13 at 16:30
  • 1
    @DanielFischer: Overlapping instances are only *necessary* to do disreputable stuff that (arguably) should violate the fundeps, with `TypeEq` the canonical example. Overlaps should give the same result where they coincide--type families *are* allowed to overlap when syntactically identical, with fundeps GHC will trust you if it can't prove a conflict. Where fundeps really shine is in expressing complicated interdependencies without polluting the namespace or needing a mile-long context full of `~` constraints. – C. A. McCann Jan 07 '13 at 13:54
4

The immediate cause for the errors is that

instance Func (Dot f1 f2) a c where apply (Dot f1 f2) = (apply f2) . (apply f1)

says you have an instance for any types a and b with Dot, these have nothing to do with the a and b used for constructing the Dot.

Adding the context (Func f1 a b, Func f2 b c) to the instance declaration makes it compile with ghc-7.2, but not with 7.4 or 7.6.

Adding the types a and c as type parameters to Dot,

data Dot f1 f2 a c where
  Dot :: (Func f1 a b, Func f2 b c) => f1 -> f2 -> Dot f1 f2 a c

makes it compile with all those GHCs.

Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
  • Do you know why the behavior depends on the GHC version? What changed? – gspr Jan 05 '13 at 14:38
  • No, unfortunately not. I know that the type-checking changed, but I'm not familiar enough with it to know why the old one was happy with just the constraint. – Daniel Fischer Jan 05 '13 at 14:41