Skip to main content
I meant species
Source Link
Theraot
  • 9.2k
  • 2
  • 28
  • 35
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 speciespecies() -> String;
}
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 speciespecies() -> String
    {
        "Canine".to_string()
    }
}
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 speciespecies() -> String
    {
        "Ovine".to_string()
    }
}
fn test<T: Animal>(animal: &T) {
    println!("{}", T::speciespecies());
    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);
}
println!("{}", T::speciespecies());
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;
}
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()
    }
}
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()
    }
}
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);
}
println!("{}", T::specie());
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 species() -> String;
}
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 species() -> String
    {
        "Canine".to_string()
    }
}
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 species() -> String
    {
        "Ovine".to_string()
    }
}
fn test<T: Animal>(animal: &T) {
    println!("{}", T::species());
    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);
}
println!("{}", T::species());
added 661 characters in body
Source Link
Theraot
  • 9.2k
  • 2
  • 28
  • 35

One thing I haven't seen (…) is using a type as a value that can be passed around, allowing it to instantiate new objects, call static functions etc, while still providing all the benefits of strong type checking

Emphasis mine.

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.

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 objectstatic member function, based on the passed object

Emphasis mine.

This provides "all the benefits of strong type checking". However, of course, has the drawback of forcing you to enumerate the possible types.

If the type is to be passed at runtime - for example, in a variable, as the question suggests - it means it is not known at compile time, so we lose that strong type checking. To pass a type at runtime but have some type information in compile time, we have generic type arguments and constraints, of course. Yet, that won't give you access to static members.

I only see two paths to keep such strong type checking: We interrogate the object, as shown above. Or stronger generic constraints…

Another thing that gets close is static interface methods. If we can make a generic constraint to such interface, we could be able to use those static members.

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.

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

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.

One thing I haven't seen (…) is using a type as a value that can be passed around, allowing it to instantiate new objects, call static functions etc, while still providing all the benefits of strong type checking

Emphasis mine.

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.

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

Emphasis mine.

This provides "all the benefits of strong type checking". However, of course, has the drawback of forcing you to enumerate the possible types.

If the type is to be passed at runtime - for example, in a variable, as the question suggests - it means it is not known at compile time, so we lose that strong type checking. To pass a type at runtime but have some type information in compile time, we have generic type arguments and constraints, of course. Yet, that won't give you access to static members.

I only see two paths to keep such strong type checking: We interrogate the object, as shown above. Or stronger generic constraints…

Another thing that gets close is static interface methods. If we can make a generic constraint to such interface, we could be able to use those static members.

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.

Source Link
Theraot
  • 9.2k
  • 2
  • 28
  • 35

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!