Skip to content

Instantly share code, notes, and snippets.

@DarinM223
Last active March 31, 2023 03:35
Show Gist options
  • Save DarinM223/f6adf64569b55408886313cd3032c7e6 to your computer and use it in GitHub Desktop.
Save DarinM223/f6adf64569b55408886313cd3032c7e6 to your computer and use it in GitHub Desktop.
Typeclasses in Haskell, Scala, and Rust

Type classes

Type classes have some advantages over Java style interfaces. One advantage is the ability to implement functions that do not need to take in an instance of the interface.

For example, (FromString v) takes in a String and returns v, and it doesn't have to take v as a parameter.

class FromString v where
    fromString :: String -> v
    
instance FromString Int where
    fromString s = read s :: Int

instance FromString Double where
    fromString s = read s :: Double
    
add :: (FromString a, Num a) => String -> String -> a
add a b = fromString a + fromString b

In Scala, type classes are represented as traits that take in a type parameter and constraints in functions are written as implicit variables.

trait FromString[N] {
  def fromString(s: String): N
}

implicit object IntFromString extends FromString[Int] {
  override def fromString(s: String): Int = s.toInt
}
implicit object DoubleFromString extends FromString[Double] {
  override def fromString(s: String): Double = s.toDouble
}

def add[N](a: String, b: String)(implicit ev1: Numeric[N], ev2: FromString[N]): N =
  ev1.plus(ev2.fromString(a), ev2.fromString(b))

In Rust, traits are very similar to typeclasses. A trait must either take in a parameter with self/Self or return something with Self. This sounds constraining, but remember that Haskell can't do this either:

class Foo a where
    hello :: b

This means that in Haskell the functions of a typeclass must contain the original type somewhere. So the way to implement FromString in Rust is to return Self.

pub trait FromString {
    fn from_string(s: String) -> Self;
}

impl FromString for i32 {
    fn from_string(s: String) -> i32 {
        s.parse::<i32>().unwrap()
    }
}

impl FromString for f32 {
    fn from_string(s: String) -> f32 {
        s.parse::<f32>().unwrap()
    }
}

pub fn add<B>(a: String, b: String) -> B where B: Add<Output=B> + FromString {
    B::from_string(a) + B::from_string(b)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment