7

I'm trying to define a pair of class instances inductively. That is:

class Foo a b | a -> b where
  foo :: a -> b

instance (not?)Foo a => Bar a b
  foo x = ...

instance Foo a => Bar a b
  foo x = ...

The first instances determines the base action, and the seconds recursively calls foo. Is there any way to do this? A good example would be flattening a list, where in the first case it's the identity function and in the second it's a recursive application of concat.

Jonathan
  • 732
  • 5
  • 19
  • 10
    Note that in Haskell, it is impossible to be sure that a given type is *not* an instance of a given type class, because someone else could compile your code with some of their own code that provides the instance. – Dan Burton Mar 04 '12 at 01:41

2 Answers2

9

There's no way to do this directly, for a very simple reason--instance selection only looks at the "head", i.e., the part after the =>. Nothing you put in the context--the part before the =>--can influence which instance is selected.

For simple cases, you can often avoid the issue entirely, such as if there's a limited number of "base case" types. A common example there would be type-level lists, where you'd have a recursive case for Cons and a base case of Nil and that's it.

In the general case, you'll typically need some sort of "conditional test" type class that picks a type based on whether some condition is fulfilled, then hand off the actual implementation to a "helper" class which takes the conditional's result value as a parameter and uses that to select an instance.

C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
  • 1
    How would you implement a generic list flatten class member then? – Jonathan Mar 03 '12 at 23:59
  • 7
    Well, the most honest answer is that I wouldn't. There's no way to do that without it breaking on polymorphic inputs and it's not useful enough to justify the hassle, in my opinion. That said, for flattening lists I think you could just rely on overlapping instances to choose between the recursive vs. base case. Are you using `OverlappingInstances` here? You'll probably need it one way or another. – C. A. McCann Mar 04 '12 at 00:02
  • 3
    Why would you want a generic list flattening function? It's rarely useful. – augustss Mar 04 '12 at 02:09
4

Here is an implementation of a flatten function that works with any level of nested list. I wouldn't really recommend using it though - here just for demonstration of how to achieve something like this in haskell.

Community
  • 1
  • 1
David Miani
  • 14,518
  • 2
  • 47
  • 66
  • 5
    Using IncoherentInstances is an invitation to trouble. – augustss Mar 04 '12 at 02:08
  • 1
    @augustss, very true, hence the disclamer I put at the top. I don't think it is possible to implement `flatten` without `IncoherentInstances` though (which is probably a very good indication that designing a program that relies on this function is a bad idea). – David Miani Mar 04 '12 at 05:02
  • 1
    @is7s: The solution there can't derive the type of the flattened list (so you have to specify it as `flatten [[3],[4]] :: [Int]`. For that t work, you need the three extensions you gave. It's likely a worthwhile tradeoff though. – David Miani Mar 04 '12 at 06:34
  • 1
    The above solution does not work for me with ghc 7.4.1. The above solution is also missing 2 extensions at least. – is7s Mar 04 '12 at 15:25
  • @is7s: Interesting, I'm using ghc 7.0.3, and it works fine. Some of the extensions automatically activate other extensions, maybe that changed for 7.4.1? – David Miani Mar 05 '12 at 00:54
  • 1
    I'm curios to know more about why it's not supported in ghc 7.4.1. It's also not supported in ghc 7.2.1. Which change to these versions stopped the support for this declaration? I'll try to find more about it. – is7s Mar 05 '12 at 14:26
  • @is7s: Well this is embarrassing, it turns out I had `:set -fglasgow-exts` in my ghci file - it didn't matter what language extensions I used the code would have worked in ghci for me. I've fixed the code example to work correctly now, although I am contemplating just removing it - it had a lot more problems than I originally thought when I wrote it. – David Miani Mar 05 '12 at 23:22
  • @nanothief Indeed, after the corrections you've made, it's not working correct (the results are not as you've shown)! Do you have an explanation for this? or is it ghc-version issue?. Sorry for the hassle anyways :) – is7s Mar 06 '12 at 19:18
  • 1
    @is7s: I have no explanation for this. I can't think of any environmental issues that could be left (my ~/.ghc/ghci.conf file is empty), and I don't know of any changes to ghc that would cause this to fail. In any case, the linked solution is better, and it seems a bit of a waste of time getting my inferior solution working, so I've just deleted my code and using the link as an example of writing a nested list flattener. – David Miani Mar 06 '12 at 23:49