Are there examples where this actually leads to clearer code in the wild?
Are there examples where this actually leads to clearer code in the wild?
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" 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 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!
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 = DSo 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.
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/