Polymorphism question from an OO-speaking newbie

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Polymorphism question from an OO-speaking newbie

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-

--
Beauty of style and harmony and grace and good rhythm depend on
simplicity. - Plato
Reply | Threaded
Open this post in threaded view
|

Polymorphism question from an OO-speaking newbie

Jan Jakubuv-2
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.

Reply | Threaded
Open this post in threaded view
|

Polymorphism question from an OO-speaking newbie

Jan Jakubuv-2
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.

Reply | Threaded
Open this post in threaded view
|

Re: Polymorphism question from an OO-speaking newbie

Ertugrul Söylemez
In reply to this post by Joel Neely
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 <[hidden email]> 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)?
>
> 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/


Reply | Threaded
Open this post in threaded view
|

Polymorphism question from an OO-speaking newbie

Brent Yorgey-2
In reply to this post by Joel Neely
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

Reply | Threaded
Open this post in threaded view
|

Re: Polymorphism question from an OO-speaking newbie

Ertugrul Söylemez
In reply to this post by Ertugrul Söylemez
Ertugrul Soeylemez <[hidden email]> wrote:

> [...]  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/