Polymorphism question from an OO-speaking newbie

Short version: Is it possible/reasonable to define a single function that accepts a single string or a list of strings (in which case it maps the single-string "flavor" over the list)? Longer version: Thus far I know that Haskell allows me to define a function on a single string, then define another function that maps the first one across a list of strings, as in: *Main> let quote1 s = "\"" ++ s ++ "\"" *Main> "Quux said " ++ quote1 "foo" ++ " loudly" "Quux said \"foo\" loudly" *Main> let quote = map quote1 *Main> quote ["foo", "baz", "bletch"] ["\"foo\"","\"baz\"","\"bletch\""] (BTW, is it standard terminology to say that quote lifts quote1 to lists?) In the above I have different names for the two functions. OO languages such as Java allow me to overload quote to accept either String or List<String> (and return the same type). AFAICT, Haskell's parametric polymorphism allows me to define functions (e.g. length) which deal with "listness" concepts indifferent to the type contained by the list. Am I missing something, or should I admit to OO wrongheadedness and accept that my inability to write a single declaration unifying: quote :: String -> String quote :: [String] -> [String] is an emphatic clue that I should change my expectations? Thanks in advance for any enlightenment shed my way! -jn- -- Beauty of style and harmony and grace and good rhythm depend on simplicity. - Plato

On Mon, May 04, 2009 at 10:34:09AM -0500, Joel Neely wrote:
Short version: Is it possible/reasonable to define a single function that accepts a single string or a list of strings (in which case it maps the single-string "flavor" over the list)?
Hi Joel, you are looking for type classes (http://haskell.org/tutorial/classes.html). They allow you to use the same function name for different types. This is called ``ad-hoc'' polymorphism and you can define different implementation of the overloaded function for different types (which is your case). Haskell also support ``parametric'' polymorphism which can be used when you have the same implementation for different types (like the function `head` which returns the first member of a list independently on the type of elements). Try the following: {-# OPTIONS -fglasgow-exts #-} quoteS s = "\"" ++ s ++ "\"" quoteL l = map quoteS l class Quatable q where quote :: q -> q instance Quatable String where quote s = "\"" ++ s ++ "\"" instance Quatable [String] where quote = map quote Then you can use it as follows: *Main> "Quux said " ++ quote "foo" ++ " loudly" "Quux said \"foo\" loudly" *Main> quote ["foo", "baz", "bletch"] ["\"foo\"","\"baz\"","\"bletch\""] Don't forget the line `{-# OPTIONS -fglasgow-exts #-}` which enables some features of GHC you need for this example (but not necessarily for all examples using type classes). Alternatively you can use the line {-# LANGUAGE TypeSynonymInstances,FlexibleInstances #-} which lists necessary extensions explicitly. Sincerely, Jan. -- Heriot-Watt University is a Scottish charity registered under charity number SC000278.

On Mon, May 04, 2009 at 05:23:59PM +0100, Jan Jakubuv wrote:
Try the following:
{-# OPTIONS -fglasgow-exts #-}
quoteS s = "\"" ++ s ++ "\"" quoteL l = map quoteS l
I forgot to say that the functions `quoteS` and `quoteL` are just for illustration. You can miss them. Jan. -- Heriot-Watt University is a Scottish charity registered under charity number SC000278.

Hello Joel,
the polymorphism concept in Haskell's type system is not the same as
OOP's polymorphism. OOP polymorphism allows you to build abstract
interfaces, whereas Haskell's polymorphism allows you to generalize both
statements and functionality.
There is no way to 'select' String and [String], as those two types have
almost nothing in common. If you find types that have something in
common, you can express it. "Having something in common" always means:
there is a set of functions, which is defined for both.
For example, the two types Integer and Float have in common that they
are numeric types. Technically this means that (+), (-), (*) and a few
other functions are defined for them. If that's all you need to know,
you can generalize the function
foo :: Integer -> Integer -> Integer
foo a b = a*a + b*b
to the following:
foo :: Integral i => i -> i -> i
foo a b = a*a + b*b
Now 'foo' is defined for every type, which is an instance of the class
Integral. Being an instance of that class precisely means that (+), (*)
and some other functions are defined for the particular type. Since
this is all you need to know for 'foo', there is no reason to restrict
it to Integer.
This can be applied to your example, but not verbatim. As said, the two
types String and [String] have little in common. If you want to write a
function with a polymorphic type, you always need to think in terms of
things you know about the types.
The types 'Maybe a' and '[a]' have a common property: [] and Maybe are
both functors. Informally that means that they are types, which
implement some notion of 'mapping a function over the value(s)'.
Technically it means that the 'fmap' function is defined for both:
fmap :: Functor f => (a -> b) -> f a -> f b
Here is your original function:
quote :: [String] -> [String]
quote = map (\s -> "\"" ++ s ++ "\"")
Since the 'map' function is actually just a special case of 'fmap',
which is constrained to lists, knowing that you can generalize 'quote'
to any functor:
quote :: Functor f => f String -> f String
quote = fmap (\s -> "\"" ++ s ++ "\"")
To answer your original question: It is impossible to write a function,
which handles both cases, because the two cases are inherently
incompatible. In other words: Trying to write a Haskell function,
which handles both cases, is pointless by concept.
Greets,
Ertugrul.
Joel Neely
Short version: Is it possible/reasonable to define a single function that accepts a single string or a list of strings (in which case it maps the single-string "flavor" over the list)?
Longer version: Thus far I know that Haskell allows me to define a function on a single string, then define another function that maps the first one across a list of strings, as in:
*Main> let quote1 s = "\"" ++ s ++ "\""
*Main> "Quux said " ++ quote1 "foo" ++ " loudly" "Quux said \"foo\" loudly"
*Main> let quote = map quote1
*Main> quote ["foo", "baz", "bletch"] ["\"foo\"","\"baz\"","\"bletch\""]
(BTW, is it standard terminology to say that quote lifts quote1 to lists?)
In the above I have different names for the two functions. OO languages such as Java allow me to overload quote to accept either String or List<String> (and return the same type). AFAICT, Haskell's parametric polymorphism allows me to define functions (e.g. length) which deal with "listness" concepts indifferent to the type contained by the list.
Am I missing something, or should I admit to OO wrongheadedness and accept that my inability to write a single declaration unifying:
quote :: String -> String quote :: [String] -> [String]
is an emphatic clue that I should change my expectations?
Thanks in advance for any enlightenment shed my way!
-jn-
-- nightmare = unsafePerformIO (getWrongWife >>= sex) http://blog.ertes.de/

Ertugrul Soeylemez
[...] If that's all you need to know, you can generalize the function
foo :: Integer -> Integer -> Integer foo a b = a*a + b*b
to the following:
foo :: Integral i => i -> i -> i foo a b = a*a + b*b
Now 'foo' is defined for every type, which is an instance of the class Integral. Being an instance of that class precisely means that (+), (*) and some other functions are defined for the particular type. Since this is all you need to know for 'foo', there is no reason to restrict it to Integer.
Sorry, I actually meant the Num class, not the Integer class, but the example is still valid, because any Integral type is also a Num type. So here is a more general 'foo': foo :: Num a => a -> a -> a foo a b = a*a + b*b Greets, Ertugrul. -- nightmare = unsafePerformIO (getWrongWife >>= sex) http://blog.ertes.de/

On Mon, May 04, 2009 at 10:34:09AM -0500, Joel Neely wrote:
Short version: Is it possible/reasonable to define a single function that accepts a single string or a list of strings (in which case it maps the single-string "flavor" over the list)?
Short answer: no. For the long answer (no, but sort of yes) see below. =)
Longer version: Thus far I know that Haskell allows me to define a function on a single string, then define another function that maps the first one across a list of strings, as in:
*Main> let quote1 s = "\"" ++ s ++ "\""
*Main> "Quux said " ++ quote1 "foo" ++ " loudly" "Quux said \"foo\" loudly"
*Main> let quote = map quote1
*Main> quote ["foo", "baz", "bletch"] ["\"foo\"","\"baz\"","\"bletch\""]
(BTW, is it standard terminology to say that quote lifts quote1 to lists?)
There are several standard ways to say it, but that is indeed one of them.
In the above I have different names for the two functions. OO languages such as Java allow me to overload quote to accept either String or List<String> (and return the same type). AFAICT, Haskell's parametric polymorphism allows me to define functions (e.g. length) which deal with "listness" concepts indifferent to the type contained by the list.
Right, and this is the key: *indifferent* to the type contained by the list. The implementation works in *exactly the same way* for any type you might wish to stick in. Java has this, too, with generics. List<foo> is defined independently of foo; you can stick any foo in you like and it works in the same way. However, the overloading you're talking about is known as 'ad-hoc' polymorphism. It means that you can give a *different* implementation for each type. You can't simulate this with parametric polymorphism. If you write a Haskell function foo :: a -> ... foo x = ... and you want foo to behave differently depending on the type of x, you can't do it. There's no way in the ... to decide what to do based on x's type; the implementation has to be the same no matter what type x is. However! You can get something similar to ad-hoc polymorphism with Haskell's type classes. Using type classes, you could do something like this: class Quotable q where -- any type which is an instance of Quotable must provide quote :: q -> q -- an implementation of quote instance Quotable String where quote = quote1 instance Quotable [String] where quote = map quote1 Now quote has type quote :: (Quotable q) => q -> q and can be used on either Strings or lists of Strings. Haskell will use type inference to figure out which version of quote to use depending on the context in which it is called. -Brent
participants (4)
-
Brent Yorgey
-
Ertugrul Soeylemez
-
Jan Jakubuv
-
Joel Neely