Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Several common typeclasses in Haskell have methods that are polymorphic in the return type, and I find it extremely useful.

The bounds of a bounded type:

    minBound :: Bounded a => a
    maxBound :: Bounded a => a
Converting an integer to an enumerated type:

    toEnum :: Enum a => Int -> a
Casting between numeric types:

    fromIntegral :: (Integral a, Num b) -> a -> b
Parsing a string into a value:

    read :: Read a => String -> a
An empty instance of a container that can be concatenated:

    mempty :: Monoid m => m
An effectful action that simply returns a constant value:

    return :: Monad m => a -> m a
It leads to clearer code because you don’t need to specify so many types—either it’s clear (and inferable) from context, or you want to write generic code. For example, with Monoid (mempty & mappend) I can implement a function called mconcat, which concatenates a list of stuff:

    mconcat :: Monoid m => [m] -> m
    mconcat = foldr mappend mempty
Now I can join a list of strings, concatenate a list of lists, union a list of sets, or even the optional versions of any of those:

    mconcat [Just "foo", Nothing, Nothing, Just "bar"]
    =
    Just "foobar"


Rust also allows polymorphic return types.

    trait Bounded {
        fn min_bound() -> Self;
        fn max_bound() -> Self;
    }
Which can be used like so. Using type inference.

    fn add_min_to_set<T:Bounded, S:MutableSet<T>>(set: &mut S) {
        set.insert(Bounded::min_bound())
    }
 
Or you can be more explicit

    let mut i: u8 = Bounded::min_bound();
    while i < Bounded::max_bound() { println!("{}", i); i += 1; }
Edit: spacing, s/++/+= 1/


Sadly, my own deficiencies in being able to read Haskel are keeping that from being a compelling argument. I do intend on visiting it more later. Just, right now, I prefer the dead simple to parse lisp over this.


Translating, with some minimal explanation:

    name :: constraints_for_type => type_of_name
says "Thing named 'name' has type 'type_of_name' with constraints 'constraints_for_type'"

So,

    minBound :: Bounded a => a
    maxBound :: Bounded a => a
"minBound has type 'a' for any 'a', so long as 'a' is an instance of Bounded"

These two are more values than functions, but the polymorphism happens the same way. If you use them where some particular bounded type is expected, that's the type they evaluate to. If you use them where an unbounded type is expected, you'll get a compile error.

----

    toEnum :: Enum a => Int -> a
"toEnum has type 'Int -> a' (that is, a function from 'Int' to 'a') for any type 'a', provided that 'a' is an instance of Enum"

i.e., Convert an int to the Enum type that the context is asking for.

----

    fromIntegral :: (Integral a, Num b) => a -> b
"fromIntegral has type 'a -> b' (that is, a function from 'a' to 'b') for any types 'a' and 'b' where 'a' is an integral type and 'b' is a numeric type"

The tuple syntax just means that all these constraints need to apply. I'm not actually certain why the syntax requires it.

----

    read :: Read a => String -> a
"read has type 'String -> a' for any type 'a', provided that 'a' is an instance of Read"

----

    mempty :: Monoid m => m
"mempty has type 'm' for any type 'm' (different letter is just stylistic - m for monoid), provided that 'm' is an instance of Monoid"

This is particularly interesting when you start doing polymorphic things with fold and friends.

----

    return :: Monad m => a -> m a
"return has type 'a -> m a', so long as 'm' is a Monad"

Here we see a "higher-kinded type" - m is a function at the type level that takes a type argument and produces another type, like a C++ template.

e.g. List parameterized by Integer gives us a List of Integers (List is spelled [] in Haskell)

----

    mconcat :: Monoid m => [m] -> m
"mconcat is a function from any 'list of m' to a single 'm', provided 'm' is an instance of Monoid"

    mconcat = foldr mappend mempty
"we define mconcat to be the right fold of mappend over the list, using mempty as our initial value"


I'm happy with myself for at least mostly getting what those were. I think I'd have to see more uses to really see the benefit, though.

I am curious on minBound and maxBound. They seem to be the same... What distinguishes them? Or, you are just saying these are two values that are defined. And they can only be given a value of a type that can be bounded?

Also, thanks for expanding!


Hopefully hornetblack's answer shed some light, but the most important bit is that the most of the above were just type signatures. If they're part of a typeclass you would define them for a particular type in the instance. Otherwise, you'd define them generically using only other functions defined to generically work with the same constraints (or looser).

So in the case of minBound and maxBound in particular, you'd define them appropriately for a particular type when you declare a type to be Bounded.


minBound and maxBound are typeclass functions. Typeclass's are a similar to java interfaces.

So the Bounded typeclass is defined as

    class Bounded a where
        minBound :: a
        maxBound :: a
If you have a function and you want the argument to be Bounded you write

    allLessThan :: (Enum a, Bounded a)=> a -> [a]
    allLessThan x = [minBound..x]
Here `a` is a generic type. That implements the Enum and Bounded typeclass. You could just write `allLessThan x = [minBound..x]` and Haskell with infer the type classes by itself.

To implement the typeclass you use `instance`, like so:

    data MyType = A | B | C | D
    instance Bounded MyType where
        minBound = A
        maxBound = D




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: