views:

206

answers:

4

When type X is defined as:

data X = 
    X { sVal :: String } |
    I { iVal :: Int } |
    B { bVal :: Bool }

and I want the Int inside an X value, if there is one, otherwise zero.

returnInt :: X -> Int

How can I determine which type of X the argument to returnInt is?

+12  A: 

Use pattern matching.

returnInt :: X -> Int
returnInt (I x) = x
returnInt _     = 0
PiotrLegnica
+3  A: 

Given a function like this:

returnInt :: X -> Int
returnInt x = {- some integer -}

...the type of x is always X. What you care about is whether x uses the X, I or B type constructor.

Use pattern matching to tell the difference:

returnInt :: X -> Int
returnInt (X _) = error "needed an Int, got a String"
returnInt (I { iVal = n }) = n
returnInt (B _) = error "needed an Int, got a Bool"
Tim Robinson
+10  A: 

Use a more flexible definition for all possible X values:

returnInt :: X -> Maybe Int
returnInt (I i) = Just i
returnInt _ = Nothing

Then you can use maybe for the particular defaulting you want—0 might be a valid value (this is known as the semipredicate problem):

*Main> maybe 0 id (returnInt $ X "")
0
*Main> maybe 0 id (returnInt $ I 123)
123
*Main> maybe (-1) id (returnInt $ X "yo")
-1

In contrast, partial functions risk runtime exceptions:

*Main> let returnInt (I i) = i
*Main> :t returnInt
returnInt :: X -> Int
*Main> returnInt (B True)
*** Exception: <interactive>:1:4-22: Non-exhaustive patterns in function returnInt

If you're feeling really froggy, you could use MonadPlus

returnInt :: (MonadPlus m) => X -> m Int
returnInt (I i) = return i
returnInt _ = mzero

to gain even more flexibility:

*Main> maybe 0 id (returnInt $ X "")
0
*Main> maybe 0 id (returnInt $ I 123)
123
*Main> returnInt (I 123) `mplus` returnInt (I 456) :: [Int]
[123,456]
Greg Bacon
+2  A: 

Just to clarify a point here, let me rewrite your data type to avoid ambiguities in the meaning of X:

data SomeType = X { myString :: String} | I {myInt :: Int} | B {myBool :: Bool}

In this definition there are no X, I and B types. X, I and B are constructors that create a value of type Sometype . Note what happens when you ask ghci what is the type of any value constructed with those type constructors:

*Main> :t (I 5)
(I 5) :: Sometype 

*Main> :t (B False)
(B False) :: Sometype

They belong to the same type!!

Just as you can use X, I and B to construct types, you can use pattern matching to deconstruct the type, like done in the other answers above:

returnInt :: SomeType -> Int 
returnInt (I x) = x        -- if the pattern matches (I x) then return x
returnInt _  = error "I need an integer value, you moron"  -- throw an error otherwise

Just remember that pattern matching occurs in order: if the value matches the pattern in some line, the patterns in lines below that will not be executed.

Note that when you define your type like you did, using what is called Record Syntax (just look here: http://en.wikibooks.org/wiki/Haskell/More%5Fon%5Fdatatypes ), you got functions like that for free!!

Try looking on the type of myInt, for example:

*Main> :t myInt
myInt :: SomeType -> Int

And look what this function do:

*Main> myInt (I 5)
5

*Main> myInt (B False)
*** Exception: No match in record selector Main.myInt

This is exactly the behavior of returnInt above defined. The strange error message just tells you that the function don't know how to deal with a member of the type SomeType that doesn't match (I x).

If you define your type using the more common syntax:

data SomeType2 = X String | I Int | B Bool

then you loose those nice record functions.

The error messages terminate the execution of the program. This is annoying sometimes. If you need safer behavior for your functions GBacon's answer is just the way to do it. Learn about the Maybe a type and use it to cope with this kind of computation that need to return some value or return nothing ( try this: http://en.wikibooks.org/wiki/Haskell/Hierarchical%5Flibraries/Maybe ).

Rafael S. Calsaverini