[Haskell-cafe] Should there be a haskell template for inclusion polymorphism in Haskell?

Miao ZhiCheng miao at decentral.ee
Fri May 27 10:42:36 UTC 2022


 > Hi Miao,
 >
 > interface Num {
 >   Num (+)(Num a, Num b);
 >   Num abs(Num a);
 >   Num fromInteger(Integer);
 >   ...
 > }
 >
 > I'm trying to follow through this and I'm afraid I'm not seeing how your
example OOP interface would work.
 > What would the implementation of `AnyNum<Double>.abs` be?  It seems like
it has no way of knowing whether the argument actually contains a Double or
not?
 >
 > Wouldn't the `Num` interface instead need to be along the lines of the
following?
 >
 > ```
 > interface Num<T> {
 >   T (+)(T a, T b);
 >   T abs(T a);
 >   T fromInteger(Integer);
 > }
 > ```
 >
 > or possibly
 >
 > ```
 > interface Num<T> {
 >   Num<T> (+)(Num<T> a, Num<T> b);
 >   Num<T> abs(Num<T> a);
 >   Num<T> fromInteger(Integer);
 > }
 > ```

Hi Aaron,

You are right, I was a bit rushing in the end, and I should write a proper
example instead of pseudo code. Here is a
rewrite in actual C++:
https://github.com/hellwolf/haskell-examples/blob/master/2022-05-25-inclusion-polymorphism/AnyNum.cpp

While re-writing, it's clear that you cannot fit Num class in OOP, in C++
it's rather using generics

```c++
// generics/compile-time ad-hoc "type classes"
// T: type of the number
// C: container of the number
template <typename T, typename C> class Num {
    typedef Num<T, C> ThisNum;

protected:
    int _val;

public:
    Num(int val) { _val = val; }

    // default implementation should work for int/double/etc.
public:
    static C add(const ThisNum& a, const ThisNum& b) { return C(a._val +
b._val); }
    static C mul(const ThisNum& a, const ThisNum& b) { return C(a._val *
b._val); }
    static C abs(const ThisNum& a) { return C(std::abs(a._val)); }
    static C negate(const ThisNum& a) { return C(std::negate(a._val)); }
    static C signum(const ThisNum& a) { return C(T(std::signbit(a._val))); }
    static C fromInteger(T x) { return C(x); }
};
```

This looks very much similar to the Haskell `Num` type class.

And the OOP-style stuff for Num and Show could rather be:

```c++
// run-time/inclusion polymorphism classes:
class INum {
public:
    virtual void operator +=(const INum&) = 0;
    virtual void operator *=(const INum&) = 0;
};
class IShow {
public:
    virtual std::string show() const = 0;
};
class IAnyNum: public INum, public IShow {};
```

So the question back to can we express the += and *= inclusion polymorphism
for Haskell data types.

It seems to me that sub-typing libraries Olaf suggested could be something
to look into.

 >
 > I'm thinking that the OOP version for your original Num interface would
have the same unsafe casting issues that your haskell translation has.
 > Or could you add an example of how `AnyNum<Double>.abs` would be
implemented in the OOP version?

Yes, indeed, without using `Typeable`, there is unsafe casting issue. In
the C++ version I fixed, it uses dynamic_cast,
which is basically using RTTI (similar to Haskell Typeable) to throw a
run-time error if mismatched.

Also you were right, there is no sensible abs for the OOP version. The OOP
Num should be "object-oriented" ones such as
"+=", "*=", etc.

Miao,

On Fri, May 27, 2022 at 4:20 AM Aaron VonderHaar <gruen0aermel at gmail.com>
wrote:

> Hi Miao,
>
> > ```
> > interface Num {
> >   Num (+)(Num a, Num b);
> >   Num abs(Num a);
> >   Num fromInteger(Integer);
> >   ...
> > }
> > ```
>
> I'm trying to follow through this and I'm afraid I'm not seeing how your
> example OOP interface would work.
> What would the implementation of `AnyNum<Double>.abs` be?  It seems like
> it has no way of knowing whether the argument actually contains a Double or
> not?
>
> Wouldn't the `Num` interface instead need to be along the lines of the
> following?
>
> ```
> interface Num<T> {
>   T (+)(T a, T b);
>   T abs(T a);
>   T fromInteger(Integer);
> }
> ```
>
> or possibly
>
> ```
> interface Num<T> {
>   Num<T> (+)(Num<T> a, Num<T> b);
>   Num<T> abs(Num<T> a);
>   Num<T> fromInteger(Integer);
> }
> ```
>
> I'm thinking that the OOP version for your original Num interface would
> have the same unsafe casting issues that your haskell translation has.
> Or could you add an example of how `AnyNum<Double>.abs` would be
> implemented in the OOP version?
>
> --Aaron V.
>
> On Thu, May 26, 2022 at 3:18 AM Miao ZhiCheng via Haskell-Cafe <
> haskell-cafe at haskell.org> wrote:
>
>> Hi Haskellers!
>>
>> Here are some of my thoughts about polymorphism in Haskell especially the
>> inclusion polymorphism, with questions in the
>> end.
>>
>> ~IGNORE THESE LHS BOILER PLATES~
>>
>> > {-# LANGUAGE GADTs            #-}
>> > module Main where
>> > import Unsafe.Coerce ( unsafeCoerce )
>>
>> ~IGNORE THESE LHS BOILER PLATES~
>>
>> * Classification of Polymorphism
>>
>> Let's assume that we are following the classification of polymorphism
>> where there are four types of it:
>>
>> ?? Btw, does anyone have a good citation of this classification? I got it
>> from all over the Internet instead:
>> e.g
>> https://www.tutorialspoint.com/types-of-polymorphisms-ad-hoc-inclusion-parametric-and-coercion
>>
>> ** Overloading Polymorphism
>>
>> It allows function with same name to act in different manner for
>> different types.
>>
>> ** Coercion Polymorphism
>>
>> Also called as casting sometimes.
>>
>> ** Inclusion Polymorphism
>>
>> Also called as subtyping. This allows to point derived classes using base
>> class pointers and references.
>>
>> ** Parametric Polymorphism
>>
>> Also called early Binding, it allows to use same piece of code for
>> different types. We can get it by using templates.
>>
>> ** Sub Classifications
>>
>> In terms of being ad-hoc or universal:
>>
>> - Overloading and Coercion are ad-hoc,
>> - Inclusion and Parametric are universal.
>>
>> In terms of being compile-time vs run-time:
>>
>> - Coercion, Overloading, Parametric are all compile-time polymorphism,
>> - while only Inclusion is run-time polymorphism.
>>
>> * Different Polymorphism in Haskell
>>
>> In terms of their counter parties in Haskell language:
>>
>> ** Overloading Polymorphismin Haskell
>>
>> I think type classes basically provides such overloading ad-hoc
>> polymorphism.
>>
>> Some thinks so too:
>> https://stackoverflow.com/questions/6636107/how-does-haskell-handle-overloading-polymorphism
>>
>> > class Fooable a where
>> >     foo :: a -> Int
>> > instance Fooable Int where
>> >     foo = id
>> > instance Fooable Bool where
>> >     foo _ = 42
>>
>> ** Coercion Polymorphism in Haskell
>>
>> I think Haskell has both `Coercible` & `unsafeCoerce` for handling
>> representational equality in compile time.
>>
>> But I am not sure if c/c++ style of conercion between similar types such
>> as int/float/double is something Haskell would
>> want to inject these magic conversion code ever.
>>
>> ** Parametric Polymorphism in Haskell
>>
>> This is probably what Haskell is best at the most, demonstrated by the
>> classic `map` function definition:
>>
>> > map' :: (a -> b) -> [a] -> [b]
>> > map' _ []     = []
>> > map' f (x:xs) = f x : map' f xs
>>
>> Note that, one could also combine parametric and overloading together
>> using constraints:
>>
>> > double :: Num a => a -> a
>> > double x = x + x
>>
>> This `double` function is parametric, but constrained only to work with
>> Num type classes, hence their implementations
>> are overloaded.
>>
>> ** Inclusion Polymorphism in Haskell
>>
>> Ok, finally inclusion polymorphism is what I want to focus on and have
>> some questions here.
>>
>> Before that, I do want to mention one observation, inclusion polymorphism
>> probably is most likely the old OOP habits
>> that die hard. For me at least, since I consider myself a recovering OOP
>> run-time polymorphism addict, and my guess is
>> that unless the use case do need run-time polymorphism, e.g. run-time
>> execution layer abstration, it is often better off
>> using other type of polymorphism to achieve the same result. And even
>> Bjarne Stroustrup is flirting with the idea:
>> [[https://www.youtube.com/watch?v=xcpSLRpOMJM][Bjarne Stroustrup -
>> Object Oriented Programming without Inheritance - ECOOP 2015]]
>>
>> Let's think of translating this piece of pseudo OOP style code into
>> haskell.
>>
>> ```
>> interface Num {
>>   Num (+)(Num a, Num b);
>>   Num (*)(Num a, Num b);
>>   Num abs(Num a);
>>   Num negate(Num a);
>>   Num signum(Num a);
>>   Num fromInteger(Integer);
>> }
>>
>> interface Show {
>>   String show(Show a);
>> }
>>
>> class AnyNum<T> implements Num, Show {
>>   // trivially implements Num Show interfaces
>> }
>>
>> // Now we can use AnyNum<Int>, AnyNum<Double>, etc. through their Num or
>> Show interfaces.
>> ```
>>
>> In Haskell though, thanks to the ability of having existential type,
>> AnyNum is actually quite nice to write:
>>
>> > data AnyNum where
>> >   MkAnyNum :: (Num a, Show a) => a -> AnyNum
>>
>> But in order to use AnyNum like using interfaces in our pseudo code, we
>> would have to write these boiler plates and in
>> some cases using unsafeCoerce due to not using Typeable run-time
>> information:
>>
>> > instance Num AnyNum where
>> >   (+) (MkAnyNum a) (MkAnyNum b) = MkAnyNum $ a + unsafeCoerce b
>> >   (*) (MkAnyNum a) (MkAnyNum b) = MkAnyNum $ a + unsafeCoerce b
>> >   abs (MkAnyNum a) = MkAnyNum . abs $ a
>> >   negate (MkAnyNum a) = MkAnyNum . negate $ a
>> >   signum (MkAnyNum a) = MkAnyNum . signum $ a
>> >   fromInteger = MkAnyNum . fromInteger
>> > instance Show AnyNum where
>> >   show (MkAnyNum a) = show a
>>
>> Here is some test code that demonstrate how distressful unsafeCoerce is:
>>
>> > main = do
>> >   let a = MkAnyNum(1 :: Int)
>> >   let b = MkAnyNum(2 :: Double)
>> >   let c = MkAnyNum(3 :: Int)
>> >   print $ show $ a + b
>> >   print $ show $ a + c
>>
>> Outputs are:
>> ```
>> λ>
>> "4611686018427387905"
>> "4"
>> ```
>>
>> Few observations:
>>
>> - If there is at most one occurance of the usage of the type class, there
>> is no need for the unsafeCoerce.
>> - Otherwise, unsafeCoerce is required, or otherwise those functions could
>> be left to "undefined".
>>
>> So my central questions for discussions are:
>>
>> - Is inclusion polymorphism something we should use at all in Haskell?
>> - These boiler plate code maybe better be generated using Haskell
>> template instead, is there one already, or should
>>   there be one?
>>
>>
>> Cheers,
>> Miao
>> _______________________________________________
>> Haskell-Cafe mailing list
>> To (un)subscribe, modify options or view archives go to:
>> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
>> Only members subscribed via the mailman list are allowed to post.
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20220527/c845a37f/attachment.html>


More information about the Haskell-Cafe mailing list