3

For my own understanding, I want to define a function in Haskell that takes two arguments- either both Integers, or both Chars. It does some trivial examination of the arguments, like so:

foo 1 2 = 1
foo 2 1 = 0
foo 'a' 'b' = -1
foo _ _ = -10

This I know won't compile, because it doesn't know whether its args are of type Num or Char. But I can't make its arguments polymorphic, like:

foo :: a -> a -> Int

Because then we are saying it must be a Char (or Int) in the body.

Is it possible to do this in Haskell? I thought of maybe creating a custom type? Something like:

data Bar = Int | Char
foo :: Bar -> Bar -> Int

But I don't think this is valid either. In general, I'm confused about if there's a middle ground between a function in Haskell being either explicitly of ONE type, or polymorphic to a typeclass, prohibiting any usage of a specific type in the function body.

10
  • Do both of the arguments have to be of the same type (in other words, can you pass it a char and an int)? Commented Dec 25, 2014 at 1:10
  • @ArthurLaks Both options are valid, I was specifically considering the case where you know they will be of the same type, but I think for learning purposes seeing either one would be valuable. Commented Dec 25, 2014 at 1:13
  • "Polymorphic to a typeclass" does not "prohibit any usage of a specific type in the function body"! Commented Dec 25, 2014 at 1:56
  • @DanielWagner buux :: [a] -> Int, buux [1] = 0, buux _ = -1. This does not compile- this is what I meant. We can't test that the contents of the list is an Int if we specified it as a polymorphic type, right? Commented Dec 25, 2014 at 2:03
  • 1
    @user2666425 You can do something similar, but it's rarely useful. For example, Typeable is available for almost all types and offers the typeOf operation. Commented Dec 25, 2014 at 2:29

3 Answers 3

5

You can use the Either data type to store two different types. Something like this should work:

foo :: Either (Int, Int) (Char, Char) -> Int
foo (Right x) = 3
foo (Left y) = fst y

So, for it's Left data constructor you pass two Int to it and for it's Right constructor you pass two Char to it. Another way would be to define your own algebric data type like this:

data MyIntChar = MyInt (Int, Int) | MyChar (Char, Char) deriving (Show)

If you observe, then you can see that the above type is isomorphic to Either data type.

Sign up to request clarification or add additional context in comments.

Comments

5

I'm not sure I would necessarily recommend using typeclasses for this, but they do make something like this possible at least.

class Foo a where
    foo :: a -> a -> Int

instance Foo Int where
    foo 1 2 = 1
    foo 2 1 = 0
    foo _ _ = -10

instance Foo Char where
    foo 'a' 'b' = -1
    foo _ _ = -10

6 Comments

I've come back to this answer after I've learned more about defining your own type classes and declaring a type to be an instance of a typeclass in Haskell- but I'm still not sure how to use what you've written here. I know you create a typeclass Foo and then declare Int and Char to be instances of Foo. But how can we use it? - E.g. foo 2 5 errors out, as does any other attempt to call foo. foo is defined in ghci, and has signature foo :: Foo a => a -> a -> Int. So how can I actually get a foo? 2 :: Foo -> Expecting one more argument to Foo.
@user2666425 foo (2 :: Int) 5 will do. Recall that 2 has many types.
Doesn't Haskell normally handle the conversion of 2 to whatever instance of Num is needed? Like in bar x = x + 1.0; bar 2, it converts it automatically. Why do we need to convert it to an Int manually?
@user2666425 There is no conversion, ever. It just has many possible types. When it isn't sure which of many possible types to choose, you must give it some guidance.
Conversion was the wrong word, but in bar it knows it needs to be Fractional, and as there is only a definition for Foo Int, not Foo Integer, etc, I'm just confused why it doesn't know itself what type to make 2.
|
3

You can do

type Bar = Either Int Char

foo :: Bar -> Bar -> Int
foo (Left 1) (Left 2) = 1
foo (Right 'a') (Right 'b') = -1 
foo (Left 3) (Right 'q') = 42
foo _ _ = 10

and things like that - the Either data type is precisely for mixing two types together. You can roll your own similar type like

data Quux = AnInt Int | AChar Char | ThreeBools Bool Bool Bool

It's called an Algebraic Data Type.

(I struggle to think of circumstances when it's useful to mix specifically characters and integers together - mainly it's very helpful to know where your data is and what type it is.)

That said, I write algebraic data types a lot, but I give them meaningful names that represent actual things rather than just putting random stuff together because I don't like to be specific. Being very specific or completely general is useful. In between there are typeclasses like Eq. You can have a function with type Eq a => a -> [a] -> Bool which means it has type a -> [a] -> Bool for any type that has == defined, and I leave it open for people to use it for data types I never thought of as long as they define an equality function.

2 Comments

Thank you for the help! Learning about algebraic data types helped me figure out a way to do this. My only question then is if you can hide from the person calling the function that an ADT is involved- For instance, if I'd rather them just call the function like foo 'a' 'b' rather than foo (AChar 'a') (AChar 'b'). Is it possible to abstract that detail of the implementation away from the function caller?
@user2666425 I think the tags keep us honest and explicit for sum types, and handily can be used in pattern matching, but if the syntax is getting you down, shorten the tags or use Daniel Wagner's suggestion. I think it depends on your actual use case what the best approach is.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.