Question

In oop, such as java, we can only downcast a super class into subclass when the type actually is the subclass.

But In haskell, we can simply 'downcast' a type class into any instances of that type class. Such as fromInteger which return a Num. From my point of view, it actually is a Int so it cannot be 'downcasted' to Float but it can.

Prelude System.Random> :t fromInteger a
fromInteger a :: Num a => a
Prelude System.Random> fromInteger 12 :: Int
12
Prelude System.Random> fromInteger 12 :: Float
12.0

Another example is to change Random into Int, Float and even Bool

Prelude System.Random> let (a, g) = random (mkStdGen 12) :: (Int, StdGen)
Prelude System.Random> let (a, g) = random (mkStdGen 12) :: (Double, StdGen)
Prelude System.Random> let (a, g) = random (mkStdGen 12) :: (Bool, StdGen)

We don't know what Random actually is, but we can just 'downcast' it into type of the instance, and it works 100% all the time. I don't understand why it works.

Était-ce utile?

La solution

I think you've gotten confused by mistakenly viewing typeclasses as OO classes and associating type inheritance with them. Typeclasses are very different and there's no type inheritance in Haskell, which btw isn't a weakness at all. Your examples actually demonstrate quite a lot of Haskell's power.

Let's analyze the definition of random:

random :: RandomGen g => g -> (a, g)

It has a signature g -> (a, g), which says that it takes some value g and returns some value a and some value of the same type as the input g, there are no specific types like Int or Char specified in this signature, a and g are polymorphic, meaning that they can be of absolutely any type. Then comes the constraint part RandomGen g =>, which says that actually g can be only of a type which has an instance of a typeclass RandomGen, right under the interface of the class I've linked to you'll find a list of its instances defined in the module and it will contain only RandomGen StdGen, so basically we can see g as StdGen. Then look again at the random function to find out that it actually is defined as part of the interface of a typeclass Random, which is parameterized by a type variable a, which we've already met in the signature of a function random, so this implies a Random a constraint on the definition of function random. Also see that this class in a list of its instances contains Random Int, Random Double, Random Bool.

Now let's return to your examples. By specifying a type random (mkStdGen 12) :: (Bool, StdGen) you're telling the compiler to see random as random :: StdGen -> (Bool, StdGen), from which it simply deduces, which instances of RandomGen and Random to use. Those instances actually define the type-specific behaviour of the functions, which in turn guarantees that any compilable code will make sense.

As you see, it all has absolutely nothing to do with casting.

Autres conseils

From my point of view, it actually is a Int

This is where you are wrong. There is two different implementation of fromInteger: Integer -> Int and Integer -> Float. There is absolutely no Int involved in fromInteger 12 :: Float.

As you can see from the comment of @MikeHartl Haskell type classes are not classes in OOP sense. Nor they are interfaces. In your case it may be useful to view them as C++ template classes with all methods static:

template <typename T>
class Random
{
public:
   static std::pair<T, StdGen> random(StdGen a);
}

The "casting" is not "casting" at all but an explicit qualification of template parameter:

std::pair<Int, StdGen> a = Random<Int>::random(mkStdGen 12);
std::pair<Int, Bool> a = Random<Bool>::random(mkStdGen 12);

Same goes for Num:

template <typename a>
class Num
{
public:
   static a fromInteger(Integer b);
}

int a = Num<int>::fromInteger(Integer(2222));
complex a = Num<complex>::fromInteger(Integer(3333));
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top