You're really asking two things here: "How do I write the function twice
?", and "how do I write an f
with two different types?"
Let's think about the first question. Letting Haskell infer types for the moment, let's think about what it should look like. It needs to take one argument: twice f = undefined
. twice
then returns a function which takes an argument and applies f
to it twice: twice f = \x -> f (f x)
.
But what's the type of this function? Well, x
must be of some type α
. Since we evaluate (f x)
, this means that f
must be a function that takes in an α
and returns a β
: f :: α -> β
. However, we also evaluate f (f x)
, so f
must take a β
as input as well, returning a γ
: f :: β -> γ
. Any single variable can only have one type, so this tells us that α -> β = β -> γ
, and so α = β
and β = γ
. Thus, f :: α -> α
, and so \x -> f (f x) :: α -> α
; this means that twice :: (α -> α) -> α -> α
.
This answers your first question. And you'll notice that I said above that f
must be a function from one type to the same type. This answers your second question: it is impossible to write an f
with two different types. This is because, as I said, any single variable may only have one (possibly polymorphic) type. Why? Well, among other reasons, suppose we have a variable impossible
with two type signatures, impossible :: Int
and impossible :: String
, and two bindings, impossible = 24
and impossible = "absz"
. Then what does show impossible
return? The show
function is of type show :: Show α => α -> String
; since both Int
and String
are instances of the Show
typeclass, we can't tell if this would return "42"
or "\"absz\""
. Inconsistencies like this are why we allow only one type.
All hope is not lost, however! You also mentioned using union types to implement f
. In this context, you probably mean the Either
type (although all datatypes in Haskell are a form of union types called discriminated unions). Either
is a type which takes two type parameters (just like []
, the list type, takes one); we say that it has kind [the type of a type] Either :: * -> * -> *
). Either
is the union type: Either A B
consists of all the elements of A
and all the elements of B
, lifted into Either
. As Michael Steele said, you can write your function with two type signatures as a function which returns an Either
value: f :: Either δ ε -> Either δ ε
. Note that this is a perfectly valid value to pass as a parameter to twice
, since Either δ ε
is a perfectly legal type. We define functions on Either
via pattern matching; the two constructors of Either
are Left :: δ -> Either δ ε
and Right :: ε -> Either δ ε
, for lifting the two types of values. A sample function, then, would look like
f :: Either Int String -> Either Int String
f (Left n) = Right $ "The number " ++ show n
f (Right s) = Left $ length s
-- f (Left 3) == Right "The number 3"
-- f (Right "The number 3") == Left 12
-- twice f (Left 3) == Left 12
If you really want to mimic your example and go through three types, from α
to β
to γ
, you can either use nested Either
s or define your own data type. With nested Either
s, you get
f :: Either Int (Either String Char) -> Either Int (Either String Char)
f (Left n) = Right $ Left $ "The number " ++ show n
f (Right (Left s)) = Right $ Right $ head $ drop 11 s
f (Right (Right c)) = Left $ fromEnum c
-- f (Left 42) == Right (Left "The number 42")
-- f (Right (Left "The number 42")) == Right (Right '4')
-- f (Right (Right '4')) == Left 52
-- twice f (Left 42) == Right (Right '4')
With a new type, you get:
data Either3 a b c = Left3 a | Mid3 b | Right3 c deriving (Eq, Ord, Read, Show)
f :: Either3 Int String Char -> Either3 Int String Char
f (Left3 n) = Mid3 $ "The number " ++ show n
f (Mid3 s) = Right3 $ head $ drop 11 s
f (Right3 c) = Left3 $ fromEnum c
-- f (Left3 42) == Mid3 "The number 42"
-- f (Mid3 "The number 42") == Right3 '4'
-- f (Right3 '4') == Left3 52
-- twice f (Left3 42) == Right3 '4'
You could also define a specific data MyType = MyInt Int | MyStr String | MyChar Char
, and replace every Either3 Int String Char
with MyType
, every Left3
with MyInt
, every Mid3
with MyStr
, and every Right3
with MyChar
; this is effectively the same thing, but less general.
Note that, thanks to Haskell's currying, we can rewrite our original twice
as twice f x = f (f x)
. And in fact, even more simply, we can write this as twice f = f (.) f
, or twice = join (.)
, if we import Control.Monad
. This is irrelevant for the purposes of answering this question, but is interesting for other reasons (especially the (->) α
instance for Monad
, which I don't fully understand); you might want to take a look if you haven't seen it before.