История изменений
Исправление Miguel, (текущая версия) :
В своё время я об этом заводил баг — мне прямым текстом сказали, что мономорфизация в Rust by design и нефиг раскачивать лодку. Правда, это было несколько месяцев назад — но недавно мне в уши надули, что, якобы, появились настоящие дженерики, и я попробовал заново.
struct Nil;
struct Cons<T> {
head : int,
tail : T
}
trait Product {
fn product(&self, other: Self) -> int;
}
impl Product for Nil {
fn product(&self, _other: Nil) -> int {0}
}
impl<T : Product> Product for Cons<T> {
fn product(&self, other: Cons<T>) -> int {
self.head * other.head + self.tail.product(other.tail)
}
}
fn genprod<T : Product>(x : T, y : T, n : int) -> int {
match n {
0 => x.product(y),
_ => genprod(Cons {head : n+1, tail : x}, Cons {head : n*n+2, tail: y}, n-1)
}
}
fn essence(n : int) -> int {genprod(Nil, Nil, n)}
fn main() {
println!("{}", essence(5));
}
test.rs:17:1: 22:2 error: reached the recursion limit during monomorphization
test.rs:17 fn genprod<T : Product>(x : T, y : T, n : int) -> int {
test.rs:18 match n {
test.rs:19 0 => x.product(y),
test.rs:20 _ => genprod(Cons {head : n+1, tail : x}, Cons {head : n*n+2, tail: y}, n-1)
test.rs:21 }
test.rs:22 }
невозможность писать «специализации» полиморфных функций.
Ну дык на том стоит ML — дженерик-функция пишется один раз, и работает со всеми возможными типами. И в Rust всё точно так же.
Другой вопрос, что:
1) Компилятор (не обязательно rust) в НЕКОТОРЫХ СЛУЧАЯХ использует специализированную функцию — скажем, GHC вообще этим знаменит, он буквально для каждой полиморфной функции старается нагенерить побольше специализаций. Но этот процесс никак не контролируется программистом и вообще снаружи не виден (кроме того факта, что некоторые функции работают чуть побыстрее).
2) Есть возможность (используя всякие фокусы типа overlapping instances) сделать иную реализацию тайпкласса (это уже чисто Haskell-specific, ни Rust, ни ML-языки тут ни при чём). Типа
data T a = ...
class C x where ...
instance C (T a) where ...
instance C (T Int) where ...
Однако, тут есть ловушка, которую не все понимают (и регулярно попадаются). Выбор инстанса производится на стадии компиляции. То есть, если использовать функцию из класса C в ситуации, когда конкретный тип неизвестен (и не может быть определён исходя из типов аргументов), то вне зависимости от того, окажется там на самом деле Int или что-то другое — выбрана будет общая реализация.
То есть, вот такой код:
{-# LANGUAGE ExistentialQuantification, FlexibleInstances, OverlappingInstances #-}
module Main where
data T a = T a
class C x where c :: x -> Int
instance C (T a) where c _ = 0
instance C (T Int) where c (T n) = n
data E = forall a. E a
test :: E -> Int
test (E a) = c (T a)
main =
do print $ c $ T (4 :: Int)
print $ test $ E (4 :: Int)
4
0
То есть, мы не можем утверждать, что для типа Int всегда работает специализированный вариант. Работает, но только в простых случаях.
Исправление Miguel, :
В своё время я об этом заводил баг — мне прямым текстом сказали, что мономорфизация в Rust by design и нефиг раскачивать лодку. Правда, это было несколько месяцев назад — но недавно мне в уши надули, что, якобы, появились настоящие дженерики, и я попробовал заново.
struct Nil;
struct Cons<T> {
head : int,
tail : T
}
trait Product {
fn product(&self, other: Self) -> int;
}
impl Product for Nil {
fn product(&self, _other: Nil) -> int {0}
}
impl<T : Product> Product for Cons<T> {
fn product(&self, other: Cons<T>) -> int {
self.head * other.head + self.tail.product(other.tail)
}
}
fn genprod<T : Product>(x : T, y : T, n : int) -> int {
match n {
0 => x.product(y),
_ => genprod(Cons {head : n+1, tail : x}, Cons {head : n*n+2, tail: y}, n-1)
}
}
fn essence(n : int) -> int {genprod(Nil, Nil, n)}
fn main() {
println!("{}", essence(5));
}
test.rs:17:1: 22:2 error: reached the recursion limit during monomorphization
test.rs:17 fn genprod<T : Product>(x : T, y : T, n : int) -> int {
test.rs:18 match n {
test.rs:19 0 => x.product(y),
test.rs:20 _ => genprod(Cons {head : n+1, tail : x}, Cons {head : n*n+2, tail: y}, n-1)
test.rs:21 }
test.rs:22 }
невозможность писать «специализации» полиморфных функций.
Ну дык на том стоит ML — дженерик-функция пишется один раз, и работает со всеми возможными типами. И в Rust всё точно так же.
Другой вопрос, что:
1) Компилятор (не обязательно rust) в НЕКОТОРЫХ СЛУЧАЯХ использует специализированную функцию — скажем, GHC вообще этим знаменит, он буквально для каждой полиморфной функции старается нагенерить побольше специализаций. Но этот процесс никак не контролируется программистом и вообще снаружи не виден (кроме того факта, что некоторые функции работают чуть побыстрее).
2) Есть возможность (используя всякие фокусы типа overlapping instances) сделать иную реализацию тайпкласса. Типа
data T a = ...
class C x where ...
instance C (T a) where ...
instance C (T Int) where ...
Однако, тут есть ловушка, которую не все понимают (и регулярно попадаются). Выбор инстанса производится на стадии компиляции. То есть, если использовать функцию из класса C в ситуации, когда конкретный тип неизвестен (и не может быть определён исходя из типов аргументов), то вне зависимости от того, окажется там на самом деле Int или что-то другое — выбрана будет общая реализация.
То есть, вот такой код:
{-# LANGUAGE ExistentialQuantification, FlexibleInstances, OverlappingInstances #-}
module Main where
data T a = T a
class C x where c :: x -> Int
instance C (T a) where c _ = 0
instance C (T Int) where c (T n) = n
data E = forall a. E a
test :: E -> Int
test (E a) = c (T a)
main =
do print $ c $ T (4 :: Int)
print $ test $ E (4 :: Int)
4
0
То есть, мы не можем утверждать, что для типа Int всегда работает специализированный вариант. Работает, но только в простых случаях.
Исходная версия Miguel, :
В своё время я об этом заводил баг — мне прямым текстом сказали, что мономорфизация в Rust by design и нефиг раскачивать лодку. Правда, это было несколько месяцев назад — но недавно мне в уши надули, что, якобы, появились настоящие дженерики, и я попробовал заново.
struct Nil;
struct Cons<T> {
head : int,
tail : T
}
trait Product {
fn product(&self, other: Self) -> int;
}
impl Product for Nil {
fn product(&self, _other: Nil) -> int {0}
}
impl<T : Product> Product for Cons<T> {
fn product(&self, other: Cons<T>) -> int {
self.head * other.head + self.tail.product(other.tail)
}
}
fn genprod<T : Product>(x : T, y : T, n : int) -> int {
match n {
0 => x.product(y),
_ => genprod(Cons {head : n+1, tail : x}, Cons {head : n*n+2, tail: y}, n-1)
}
}
fn essence(n : int) -> int {genprod(Nil, Nil, n)}
fn main() {
println!("{}", essence(5));
}
test.rs:17:1: 22:2 error: reached the recursion limit during monomorphization
test.rs:17 fn genprod<T : Product>(x : T, y : T, n : int) -> int {
test.rs:18 match n {
test.rs:19 0 => x.product(y),
test.rs:20 _ => genprod(Cons {head : n+1, tail : x}, Cons {head : n*n+2, tail: y}, n-1)
test.rs:21 }
test.rs:22 }
невозможность писать «специализации» полиморфных функций.
Ну дык на том стоит ML — дженерик-функция пишется один раз, и работает со всеми возможными типами.
Другой вопрос, что:
1) Компилятор в НЕКОТОРЫХ СЛУЧАЯХ использует специализированную функцию — скажем, GHC вообще этим знаменит, он буквально для каждой полиморфной функции старается нагенерить побольше специализаций. Но этот процесс никак не контролируется программистом и вообще снаружи не виден (кроме того факта, что некоторые функции работают чуть побыстрее).
2) Есть возможность (используя всякие фокусы типа overlapping instances) сделать иную реализацию тайпкласса. Типа
data T a = ...
class C x where ...
instance C (T a) where ...
instance C (T Int) where ...
Однако, тут есть ловушка, которую не все понимают (и регулярно попадаются). Выбор инстанса производится на стадии компиляции. То есть, если использовать функцию из класса C в ситуации, когда конкретный тип неизвестен (и не может быть определён исходя из типов аргументов), то вне зависимости от того, окажется там на самом деле Int или что-то другое — выбрана будет общая реализация.
То есть, вот такой код:
{-# LANGUAGE ExistentialQuantification, FlexibleInstances, OverlappingInstances #-}
module Main where
data T a = T a
class C x where c :: x -> Int
instance C (T a) where c _ = 0
instance C (T Int) where c (T n) = n
data E = forall a. E a
test :: E -> Int
test (E a) = c (T a)
main =
do print $ c $ T (4 :: Int)
print $ test $ E (4 :: Int)
4
0
То есть, мы не можем утверждать, что для типа Int всегда работает специализированный вариант. Работает, но только в простых случаях.