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)
}