Typed Polymorphism

A Comparison Between Existing Languages

Haskell: Typeclass

class FromJSON a where
    parseJSON :: Value -> Parser a

class ToJSON a where
    toJSON     :: a -> Value

data Person = Person {
      name :: Text
    , age  :: Int
    }

instance FromJSON Person where
    parseJSON = withObject "Person" $ \v -> Person
        <$> v .: "name"
        <*> v .: "age"
          
instance ToJSON Person where
    toJSON (Person name age) =
        object ["name" .= name, "age" .= age]
# PRESENTING CODE

Haskell: Typeclass & Constraints

class Semigroup a where
  (<>) :: a -> a -> a
  
instance Semigroup (List a) where
  Nil         <> ys = ys
  (Cons x xs) <> ys = Cons (xs <> ys)
  
class Semigroup a => Monoid a where
  mempty  :: a
  mappend :: a -> a -> a
  
instance Monoid (List a) where
  mempty  = []
  mappend = (<>)
# PRESENTING CODE

Haskell: Typeclass

class Functor f where
  fmap :: (a -> b) -> f a -> f b
  
data List a = Nil | Cons a (List a)
  
instance Functor List where
  fmap f Nil         = Nil
  fmap f (Cons x xs) = Cons (f x) (fmap f xs)
# PRESENTING CODE

Rust: Trait

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}
# PRESENTING CODE

Rust: Trait & Associated Type

trait Iterator {
    type Item; // Associated Type
    fn next(&mut self) -> Option<Self::Item>;
    ...
}
      
pub trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item = Self::Item>;

    fn into_iter(self) -> Self::IntoIter;
}
      
impl<'a, T, A: Allocator> IntoIterator for &'a Vec<T, A> {
    type Item = &'a T;
    type IntoIter = slice::Iter<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}
# PRESENTING CODE

Rust: Trait & Associated Type

pub struct Map<I, F> {
    pub(crate) iter: I,
    f: F,
}

impl<I, F> Map<I, F> {
    pub(in crate::iter) fn new(iter: I, f: F) -> Map<I, F> {
        Map { iter, f }
    }
}

let v: Vec<i32> = [1, 2, 3].into_iter().map(|x| x + 1).rev().collect();
# PRESENTING CODE

Rust: Trait & Associated Type

trait Iterator {
    type Item; // Associated Type
    fn next(&mut self) -> Option<Self::Item>;
    fn map<B, F>(self, f: F) -> Map<Self, F>
    where
        Self: Sized,
        F: FnMut(Self::Item) -> B,
    {
        Map::new(self, f)
    }
    ...
}

pub struct Map<I, F> {
    pub(crate) iter: I,
    f: F,
}
# PRESENTING CODE

Rust vs Haskell: map is...different

# PRESENTING CODE
  • In Rust: it's a struct, too many packed in one trait
  • In Haskell: inheritance, inheritance, inheritance

The Secret Sauce: More Types!

-- *Kind* signature
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b  
# PRESENTING CODE

Can you do this in Rust? No.

Rust: Problem with Trait Design

trait GeeksforGeeks<T> { 
    fn gfg_func(self, _: T); 
}
# PRESENTING CODE

Rust: Trait Bounds

impl<'a, T, A: Allocator> IntoIterator for &'a Vec<T, A> {
    type Item = &'a T;
    type IntoIter = slice::Iter<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}
# PRESENTING CODE

Rust: Trait Bounds

impl<'a, T, A: Allocator> IntoIterator for &'a Vec<T, A> {
    type Item = &'a T;
    type IntoIter = slice::Iter<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}
# PRESENTING CODE

C++: Template & Generic

template <class T> void bubbleSort(T a[], int n)
{
    for (int i = 0; i < n - 1; i++)
        for (int j = n - 1; i < j; j--)
            if (a[j] < a[j - 1])
                swap(a[j], a[j - 1]);
}
                          
int main()
{
    int a[5] = { 10, 50, 30, 40, 20 };
    int n = sizeof(a) / sizeof(a[0]);
     bubbleSort<int>(a, n);
 
    for (int i = 0; i < n; i++)
        cout << a[i] << " ";
    cout << endl;
 
    return 0;
}
# PRESENTING CODE

C++ Constraints: Concept

#include <string>
#include <cstddef>
#include <concepts>
 
template<typename T>
concept Hashable = requires(T a)
{
    { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
 
struct meow {};

int main()
{
    using std::operator""s;
 
    f("abc"s);    // OK, std::string satisfies Hashable
    // f(meow{}); // Error: meow does not satisfy Hashable
}
# PRESENTING CODE
# CHAPTER 2

Conclusion & Q&A

  • Typeclass: Genericity, Constraints, all in one go
  • C++ Concept: Not Good

Code

By zekt

Code

  • 74