Michael Wu
Michael Wu

Reputation: 1277

Haskell function that executes differently depending on type of variable

To be more specific, suppose I have some data constructor

data Foo = ... deriving Eq

And the following silly function

f :: Eq a => a -> Bool

In cases where the variable a is actually of type Foo, I want f to output True. In all other cases (i.e. for all other instances of Eq), I want f to output False.

At first I thought maybe I could define a new type class for this purpose

class IsFoo a where
    isFoo :: a -> Bool

While it's easy to write an instance of IsFoo for Foo, obviously I don't want to do this for all types that are instances of Eq.

When answering, you can assume that Foo has A LOT of constructors and that I don't want to pattern match on all of them. I also don't want to use Data.Typeable (I've read that it's bad style). Is there a way to accomplish what I want in an elegant and natural (w.r.t. Haskell) way?

Upvotes: 1

Views: 250

Answers (2)

Petr
Petr

Reputation: 63389

If this is really what you want to do, I'd suggest you to use Data.Typeable, because it's suited exactly for this purpose:

import Data.Maybe (isJust)
import Data.Typeable

isFoo :: (Typeable a) => a -> Bool
isFoo x = isJust (cast x :: Maybe Foo)

The question of bad style isn't about using a particular library like Data.Typeable. It's about not using Haskell's type system properly, in particular treating it like a dynamic OO language. If you need to determine if some generic type is Foo or is not, then you somewhere forgot the type information. But in Haskell, you always have this at compile time, so there shouldn't be the need of determining it dynamically.

Perhaps explain what you want to achieve, it's likely that there is a more idiomatic way how to do that.

Upvotes: 5

ThreeFx
ThreeFx

Reputation: 7360

You shouldn't need to do this in my opinion. This seems like a serious XY problem to me, since Haskell's type system should generally do this stuff for you.


But nonetheless, it is possible. The easiest way to achieve this is indeed to use typeclasses:

data Foo = A | B | C | D | ... | Z deriving Eq

class IsFoo a where
  isFoo :: a -> Bool

instance IsFoo Foo where
  isFoo = const True

instance IsFoo x where
  isFoo = const False

Using the FlexibleInstances extension, you save yourself some work by simply returning True when given an argument of type Foo, which is specified in the instance for type Foo and False when calling isFoo with a variable of any other type. Note that you also have to use the extension OverlappingInstances, otherwise a runtime error will occur calling isFoo with an argument of type Foo because the program will not know which instance to use. To enable these extensions, simply include

{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

at the top of your source file.

Still: I strongly suggest trying a different approach to your problem because in general you do not have to deal with such "low-level" typing stuff.

Upvotes: 5

Related Questions