what I want to do is to write a function get the bound of the input of a function
import Data.Int
import Data.Typeable
import Prelude
class (Bounded a, Enum a) => Finite a
instance Finite Int8 where
func::Int8->Int8
func x = x
getMaxParam::(Finite a, Eq b)=>(a->b)->a
getMaxParam f = maxBound $ typeOf a
a = getMaxParam fun
But obviously the code do not work. There are limited source online. I don't know how to deal with the class info. Can anyone help ?
CodePudding user response:
typeOf a
doesn't make any sense here, it's the wrong way around: typeOf
takes a value of some type, and gives you the type of it. (Well, that's actually not possible because types don't exist as runtime values, but TypeRep
is a runtime representation of the type.) But
You don't have a value of type
a
. Thea
variable is in the type-level language, so you can't mention it in a value-level expression; and it is, well, the type itself, not some value of that type.No runtime representation of anything is needed, because the types are perfectly known at compile time and the compiler can resolve everything without any such tricks. In fact, it can do it pretty much without any help at all:
maxBound
isn't even a function, just a polymorphic value. It doesn't need any arguments, just a context in which the result is given, which it is here.
Thus it's extremely simple: you don't even need the function!
getMaxParam :: Finite a => (a->b) -> a
getMaxParam _ = maxBound
CodePudding user response:
Take a step back, and look at the type of maxBound
:
maxBound :: Bounded a => a
maxBound
just is a value of type a
, not a function you call on anything to produce one.
To tell it what type you want the maximum bound of, you don't pass it the type you want as a parameter, you simply write maxBound
where you want the value. maxBound
is 127
if you write it where you want an Int8
, or it's True
if you write it where you want a Bool
, etc. The compiler knows the type of every single expression and sub-expression in your code1, so it already knows which type you're trying to get the maximum bound of, you don't need to pass anything to tell it.
This is how type classes work in general, even things like succ :: Enum a => a -> a
which are functions receiving a value of the type which is an instance of the class. succ
is not a single magic function that can take values of multiple types, which sees what type it receives and then decides what to do. Rather there are many different simple functions that succ
could be (one for each instance of Enum
) and the compiler decides which one to use because it knows what type of function is needed where succ
was written2. And the compiler decides what function succ
is before it is passed any input, so the input doesn't determine which function is called3.
This is a key confusion a lot of people have learning Haskell after learning how classes work in object-oriented languages. The methods of an OO class are all tied to the objects of that class, so you can usually only get access to them by calling the methods on such an object. The methods of a Haskell type class are tied to the type, and do not need to be called on a value of that type (or on anything at all, in cases like maxBound
).
So, coming back to the OP's specific problem. How do we fill in the blank here:
getMaxParam::(Finite a, Eq b)=>(a->b)->a
getMaxParam f = _
Well, the compiler already knows what the type of that blank is (and if you try to compile that it'll tell you). It's the a
from the type declaration above. So we can just write maxBound
and it'll pick the right value for us, nothing else required!
getMaxParam::(Finite a, Eq b)=>(a->b)->a
getMaxParam _ = maxBound
(Since we aren't using f
I replaced it with _
, which signals to the compiler that we weren't intending to use it; with some settings it'll emit warning messages about unused parameters otherwise).
Now note that in a = getMaxParam fun
the only purpose fun
is serving is to cause type inference to decide what type this usage of getMaxParam
has, which in turn determines that type (and value) the maxBound
inside it has. If we had a type signature for a
:
a :: Int8
a = getMaxParam fun
Then we don't really need getMaxParam
at all. Since we can just write:
a :: Int8
a = maxBound
To get the maxBound
of Int8
without reference to fun
. Whether you still want to use getMaxParam
is up to you. When writing lots of polymorphic code it often is useful to have functions like getMaxParam
which don't really "do anything", but link the types of things in useful ways. But in a bigger context where you're actually doing something with the values (say, enumerating all the possible inputs to the function and then applying the function to each of them) you probably don't need getMaxParam
; just writing maxBound
instead of getMaxParam fun
will likely work. The reason is that if you end up passing maxBound
to fun
then it's already constrained to be of same type as fun
's input parameter (you'll get a type error if that doesn't work). You don't need to write a function that analyses fun
to do that, because that analysis is built into the compiler.
1 Some of the types it knows because you told it by writing type declarations. For the rest it figures out the only possible type at each position; if it can't do that you'll get type errors.
2 The story I've told here is a little over-simplified, because you can also use class methods in positions where the type expected is not a single concrete type but a polymorphic type (where an appropriate class constraint is available). For example:
penultimate :: (Num a, Bounded a) => a
penultimate = maxBound - 1
Here there's no single value the compiler can use for maxBound
, rather the caller of penultimate
will decide (by deciding what type it needs).
In such cases, ultimately there will be some place (or places) where the compiler can see a particular concrete type it needs for maxBound
, but that decision will have to be passed around to get to penultimate
to tell it what value to use for maxBound
. That's what the constraints (the stuff left of the =>
in a type) represent; decisions about type class instances that the compiler will have to pass around.
I recommend you don't think too hard about this yet if you're just beginning. Just remember: class methods are chosen by the compiler based on the type inferred, not based on any inputs they receive.
3 Of course, type inference looks at everything you've written to decide what type each expression must be. So sometimes it's possible that the instance chosen for a class member function is in fact determined by the input. But it's the type of the input that matters, at compile time; not the value of the input at runtime, nor its runtime type.