You say:
For example, I have 2 classes, J
and K
, both of which implement interface I
and in some circumstances may be used in the same place.
And:
if the type itself can be passed I could have a function that takes a type implementing I
and call whichever version of a static member function, based on the passed object
Something that gets close is runtime interrogations. For example with simple C# pattern matching:
public static void Test<T>(T obj)
where T: I
{
if (obj is J objAsJ)
{
// use J, including static methods on J
}
if (obj is K objAsK)
{
// use K, including static methods on K
}
}
This, of course, has the drawback of forcing you to enumerate the possible types.
Another thing that gets close is static interface methods. Java has interfaces with static methods, but you can't override those. So we need a language with has something like interfaces with static members that we can override. Rust is such language.
For example, I have 2 classes, J
and K
, both of which implement interface I
and in some circumstances may be used in the same place.
I'll have two types Dog
and Sheep
, and a trait Animal
implemented for both.
if the type itself can be passed I could have a function that takes a type implementing I
and call whichever version of a static member function, based on the passed object
Traits in Rust can have static functions, which we get to implement for each type.
I'll show how to call both static and instance functions defined in a trait, getting a different result depending on the actual type. So it calls "whichever version of a static member function, based on the passed object".
The reason I'm saying it gets very close is because I'll never have a variable storing the type, which is what the title of the question suggests ("Do any programming languages use types as values?").
To be fair, Rust has TypeID
, which is just a number. It can be used to identify and compare types, but that's about it.
Instead everything is type checked at compile time (which, according to comments, seems to be what you care about). Rust does not have runtime reflection.
Note: I'll be using String
(which is a heap allocated string), and i'll be cloning it. Not efficient, but I don't bother with lifetimes.
I'll have two types Dog
and Sheep
:
struct Dog { name: String }
struct Sheep { wool: bool, name: String }
An Animal
trait:
trait Animal {
fn new(name: String) -> Self; // Self is the type that implements the trait
fn name(&self) -> String;
fn noise(&self) -> String;
fn talk(&self) {
println!("{} says {}", self.name(), self.noise()); // This is default impl.
}
fn specie() -> String;
}
And we implement the trait for both types. This is Animal
for Dog
:
impl Animal for Dog {
fn new(name: String) -> Dog {
Dog { name: name }
}
fn name(&self) -> String {
self.name.clone()
}
fn noise(&self) -> String {
"bark!".to_string()
}
fn specie() -> String
{
"Canine".to_string()
}
}
This is Animal
for Sheep
.
impl Animal for Sheep {
fn new(name: String) -> Sheep {
Sheep { name: name, wool: true }
}
fn name(&self) -> String {
self.name.clone()
}
fn noise(&self) -> String {
if self.wool {
"baaaaah!".to_string()
} else {
"baaaaah?".to_string()
}
}
fn talk(&self) {
println!("{} pauses briefly... {}", self.name, self.noise());
}
fn specie() -> String
{
"Ovine".to_string()
}
}
Let us use them:
fn test<T: Animal>(animal: &T) {
println!("{}", T::specie());
animal.talk();
let clone = T::new("Clone of ".to_owned() + &animal.name());
clone.talk();
}
fn main() {
let my_dog: Dog = Animal::new("Snuppy".to_string());
let mut my_sheep: Sheep = Animal::new("Dolly".to_string());
test(&my_dog);
test(&my_sheep);
}
As you can see the test
function is generic. It has a type argument T
that must have an implementation of Animal
. And it borrows an argument of that type.
We are able to call static functions defined in the trait:
println!("{}", T::specie());
Which outputs "Canine"
for Dog
and "Ovine"
for Sheep
.
We are able to call instance functions defined in the trait:
animal.talk();
We are able to create new instances of the same type we are given (this is just another static function):
let clone = T::new("Clone of ".to_owned() + &animal.name());
And use those those instances:
clone.talk();
Everything is type checked at compile time.
This is the output of the program:
Canine
Snuppy says bark!
Clone of Snuppy says bark!
Ovine
Dolly pauses briefly... baaaaah!
Clone of Dolly pauses briefly... baaaaah!