Question

The standard way that types are handled in programming languages that have such a concept, is that they are:

  1. removed entirely at compile time and are just used to determine memory layout, function pointers etc...
  2. left in some form as a sort of tag on a structure in memory, so that you can check if B instanceof A and similarly,
  3. types don't really exist beyond an objects properties matching at runtime what the user expects them to.

C/++ would be more or less the first, C# and Java i think would be the second, because they both have some degree of reflection and can tell what their type is, and Javascript would be a mix of 2 and 3 - most people only care about properties usually, but you can compare prototype chains to see if one inherits another

One thing I haven't seen - and im not sure if it is because no languages do it or i just haven't been able to use the right search terms - 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

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. Interfaces can only specify functions/properties that will exist on an instantiated object typically, but 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

I could then, later, use that passed type to instantiate an object of type J or K but with the visible type of I (as expected)

The closest I have seen is javascript allowing you to pass around class constructors because classes in javascript are mostly just functions with extra steps (if my understanding is correct), and even typescript will only let you treat them as Function type and not as an specific type with a specific signature, meaning you have no type safety whilst doing so.

My reason for wanting this, or atleast wondering, is related to inversion of control containers - if I could use a type as a value, i could map a property on an object to a specific type at runtime and have direct access to the static methods and constructor for that class and not need to construct other proxy objects or factories to handle those for me

Was it helpful?

Solution

Yes, any languages where classes are first class objects would allows classes to be used as value in a variable.

I don't know if there's a widely accepted name for this kind of behaviour, but this is often described as "everything is an object", where it's understood that "everything" includes classes having a runtime representation as an object.

Most modern, dynamically typed object oriented languages supports this to some degree. While in some languages like PHP runtime classes objects are really clunky because the class object are basically just souped up strings, in better ones like Python or Smalltalk, classes are true first-class objects.

For example, in Python (which is a strongly typed dynamic language), a real life example where this is used effectively is in SQLAlchemy ORM, where to make a query you pass in the model class as parameter to a function:

class User(Model): ...

users_queryset = session.query(User).order_by(User.id).all()

user: User = users_queryset.first()

or for example, you can create a "class decorator", which is a function that takes some class object and returns another, modified class object. For example, the dataclass decorator allows you to create read-only/immutable class in Python:

@dataclass(init=True, frozen=True)
class Coord2D:
    x: int = 0
    y: int = 0

if use_3d:
    Coord = Coord3D # let's assume Coord3D is defined somewhere
else:
    Coord = Coord2D

assert isinstance(Coord, type)
home = Coord(x=10, y=30)
assert isinstance(home, Coord)

def is_equal_to(value):
    def _is_equal_to(self):
        return (self.x, self.y) == value
    return _is_equal_to

# if you're the adventurous sort, you can even
# modify the class definition itself at runtime
Coord.is_zero = is_equal_to((0, 0))
assert not home.is_zero()
zero = Coord(0, 0)
assert zero.is_zero()

There's a lot of useful constructs you can do when classes are first class objects, introspection becomes trivial, and even more advanced constructs like metaclass programming which allows you to do many interesting things in runtime that would be unthinkable in languages that doesn't have classes as runtime objects.

OTHER TIPS

Do any programming languages use types as values?

Most languages that even have types (your question doesn't make sense for languages like ECMAScript, Python, Ruby, Smalltalk, Magpie, etc. which don't have types in the first place) have a strict line between the universes of types and terms. For example, most languages even allow you to use the same names for types and for terms, because the line between them is so strict that there is never a place where there could be any confusion between the two.

For untyped languages like BCPL and dynamically-typed languages like ECMAScript, Python, Ruby, Smalltalk, Magpie, etc., the answer is trivially "Yes", because they don't have types in the sense that you are asking about, and thus the statement "all types in BCPL, ECMAScript, Python, Ruby, Smalltalk, Magpie are values" is vacuously true. (But so is the statement "no types in BCPL, ECMAScript, Python, Ruby, Smalltalk, Magpie are values".)

Dependently-typed programming languages like Coq, Isabelle, Epigram, Agda, Guru, or Idris blur the line between types and values, but they mostly do it in the other direction: they allow types to "depend on" terms (hence the name "dependently-typed"). What this means is that dynamic runtime values can be used in static compile-time types. The stereotypical examples (the equivalent to Pet-Mammal-Dog in OO or Hello World in imperative programming) are dimensional vectors and sized lists.

In most programming languages, the runtime size of a list cannot be part of its type. In dependently-typed languages, however, types can depend on runtime values, so a list type can actually not only be parametric in its element but also in its runtime length. This means, for example, I can give the append function a much more precise type. Instead of

append :: List<T>, List<T> -> List<T>

[Note: I'm using some vaguely Java-ish pseudo-code in the hope that will be understandable to most readers.]

which simply says "give me two lists and I give you a list of the same type", I can actually say

append :: List<T, m>, List<T, n> -> List<T, m + n>

or in actual Idris code:

app : Vect n a -> Vect m a -> Vect (n + m) a
app Nil       ys = ys
app (x :: xs) ys = x :: app xs ys

which says "give me a list of length m and a list of length n and I give you a list of the same type and length m + n". In fact, you can encode even more properties of the list, such as the fact that all elements of the two lists must be present in the output in the same order, etc. You can actually encode any computable logical statement as a type!

Dependently-typed languages are the the most prominent and well-known example of breaking down the barrier between terms and types I know, but they do it in the opposite direction of what you are asking.

I found a paper titled Data types as values, James Edward Donahue and Alan John Demers, ACM Transactions on Programming Languages and Systems, July 1985 which describes the Russell programming language. However, the authors interpret the concept of type slightly differently than what we are used to.

Another programming language that has first-class types is Aldor

Would there be any point?

Given there are two languages that support it, at least two people seem to think so!

However, none of the situations you describe in your question require first-class types as values in any way, shape, or form. They all can be solved by first-class classes as in Smalltalk, Ruby, Python, and many, many other languages. And even though Java and C# don't have first-class classes, they make it very easy to obtain a reflective proxy object as a stand-in for a class, which can be used in the way you require.

E.g. Ruby:

class J; end
class K; end

def object_maker(klass)
  klass.new
end

j = object_maker(J)
k = object_maker(K)

Python can do the same:

class J:
    pass

class K:
    pass

def object_maker(klass):
    return klass()

j = object_maker(J)
k = object_maker(K)

And in Java you can do it with reflective proxies:

public class MyClass {
    public static void main(String args[]) {
      var j = I.classMaker(J.class);
      var k = I.classMaker(K.class);
    }
}

interface I {
    static I classMaker(Class<? extends I> klazz) {
        try {
            return klazz.getConstructor().newInstance();
        } catch (Exception e) {
            return null;
        }
    }
}

class J implements I {
    public J() {}
}
class K implements I {
    public K() {}
}

I was somewhat lazy in error handling as you can see, so that's one disadvantage of using dynamic runtime reflection to implement it: you have no type-safety unless you implement it yourself.

I think this can be done in a more type-safe manner using Scala's static compile-time reflection, but I am not up to speed with the current state of the art in Scala reflection. There are some type-safe compile-time DI/IoC implementations for Scala, so it must be possible somehow.

You say:

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.

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

Emphasis mine.

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 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.

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 species() -> 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 species() -> 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 species() -> String
    {
        "Ovine".to_string()
    }
}

Let us use them:

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

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::species());

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!

Hack supports at least a limited subset of this.

That said, it tends to be exceedingly dangerous and problematic to implement from a programming language perspective. Static methods in most languages are not part of an interface, and they do not generally dispatch since the type isn't an argument to the function. At that point, all the type system should let you call is a static method on the interface type - which you already know.

Smalltalk-80 has pioneered the concept "everything is an object" and naturally, its classes are objects, too. They can be passed around, handle method invocations, stored in variables, etc.

Of course, as they are objects, and each object has a class, they have classes, too (called metaclasses,) which are objects and can be passed around.

It is very instructive to see how the system uses this to enable a live programming experience. Although Smalltalk-80 does not provide types on variables and method parameters and thus doesn't do static type checking its IDE is still one of the most productive environments I know. There have been attempts at optional static typing but that never really took off.

Many early agile practices have been developed in or around the Smalltalk-80 system, such as refactoring and unit testing, as the uniform treatment of objects and classes makes that possible with pretty low resources. Comparable functionality for statically compiled languages only became available quite a bit later.

There are a number of free and commercial implementations available, some descending from the original Xerox PARC Smalltalk-80 system, some implemented from scratch.

In Swift, classes can be used as values. What you can do with such a value is limited. An example where I used it: A method has a parameter which is an instance of class X (or a subclass). For debugging, I wanted to print the name of the real class. So I assign object.class to a variable whichClass, and print whichClass.name. (That's from memory, the actual names are most likely different).

I don't think you could use such a value for example to create a new instance, because the compiler wouldn't know the parameters needed for that. In Objective-C you could call "alloc" which allocates memory for an instance, which on its own isn't very useful.

Licensed under: CC-BY-SA with attribution
scroll top