10

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

10
  • And we are not talking reflection, right? Edit: I mean, you have .NET Type type, for example, which will allow to do most if not all that is described here via reflection. We are not talking strings as types either, right? For example Visual Basic CreateObject or PHP new and get_class and similar which use strings.
    – Theraot
    Commented Feb 25, 2021 at 3:38
  • 1
    whether the type exists in the generated code seems like an implementation detail not a language detail to me (though there is some influence) id think that any language that supported higher kinded types essentially supports types as values irregardless of whether they are erased during compilation or not
    – jk.
    Commented Feb 25, 2021 at 8:39
  • 1
    Python (and several other dynamically typed languages) has first class classes, so will allow you to do what you describe. In a language like C# you can solve similar problem using generics, but not the exact scenario you describe, since you can't call a static method on a generic. (Unless you use reflection, but that is cheating.)
    – JacquesB
    Commented Feb 25, 2021 at 9:25
  • 1
    Given you accepted the answer by Lie Ryan, I believe I misunderstood you. Does using classes as values in Python (as described in the aforementioned answer) give you the strong type checks you want?
    – Theraot
    Commented Feb 25, 2021 at 10:57
  • 1
    Have you looked into OCaml's functors? It reminds me a bit of that. A functor is (as far as I understood, I'm no OCaml expert) a function which operates on the type level, takes a module (static class) as an argument and produces a new module based on it. Commented Feb 25, 2021 at 14:49

6 Answers 6

8

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.

11
  • 1
    The OP is asking about types, not classes. None of what you describe are types (even though both Python's type function and ECMASCript's typeof operator have "type" in their name). Commented Feb 25, 2021 at 7:28
  • 3
    @JörgWMittag: Tbh, I'm just totally confused by your comment, I'm not quite sure which distinction between type and classes you are referring to in your head when you wrote that. Different languages that distinguishes between classes and types distinguishes them in completely different, incompatible ways. Oftentimes, that's not even the official term that the language actually ever used. Java doesn't actually have a feature called "types" for example; it has classes, interface, and primitives that autobox with some classes. Type is just not a thing in that language's standard terminology.
    – Lie Ryan
    Commented Feb 25, 2021 at 9:41
  • 5
    Old versions of Python also had a separation between types and classes, much like JavaScript. All instances of classes had the same type, called "instance". stackoverflow.com/questions/54867/… Commented Feb 25, 2021 at 11:14
  • 2
    Re: "types and classes are the same thing". They are not, as already pointed out. There are strongly typed languages where there are no classes. There are also structurally typed languages where classes and types lead rather separate lives. Python for example - in the example above, you can call is_zero on any old object that defines such a method, be it an instance of Coord or not. The class is seldom interesting, the contract of the class is. Structural typing can be static as well - in TypeScript two unrelated classes with same fields & methods are subtypes of each other.
    – gustafc
    Commented Feb 25, 2021 at 11:42
  • 3
    @gustafc Can we agree that not everybody uses "type" in the same way, and while there may be a useful formal definition used in Computer Science theory, it is not the definition that most Software Engineers are familiar with? The distinction between "a class is a type" and "a class describes a type" is going to go completely over most people's heads, so if the distinction is actually useful for discussing this question, somebody needs to post an answer actually explaining it, rather than leaving comments under other answers that are quite clearly not adding to anyone's understanding.
    – IMSoP
    Commented Feb 25, 2021 at 16:55
12

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.

6
  • 3
    I think the definition of "type" that you are using in your second paragraph here, and in various comments on other answers, is not the one that a lot of readers are familiar with. Note that even the question uses "type" to include things like classes, so the "in the sense that you are asking about" seems off-base. Perhaps you could give a summary of what you understand a "type" to be, and why you think it's important to draw a hard line between that and the more colloquial use of "type" many other people are referencing.
    – IMSoP
    Commented Feb 25, 2021 at 10:17
  • @IMSoP: I think Jörg W Mittag is using the word "type" in a more narrow, more precisely defined notion that appears in functional programming and associated type theories (where a type denotes a characterized collection of values a variable might "pull from" loosely speaking). But I think even in the computer science community, this is not the only possible / universally accepted notion of types. Commented Feb 25, 2021 at 14:00
  • @FilipMilovanović Yes, I think you're right, but would like to see that definition added to this answer. In particular, I'd like to see a clearer explanation of why "class" and "type" should not be treated as overlapping concepts, to back up the assertion that Ruby, Python, Smalltalk, etc "do not have types", which is not a characterisation I've come across before.
    – IMSoP
    Commented Feb 25, 2021 at 14:06
  • 1
    Dynamically typed languages don't have types in the sense that statically typed languages do; really, the two concepts have pretty much nothing to do with each other besides etymology. Saying that Java and Python both have types is a bit like saying that decibels and frontier houses are both made of logs.
    – trent
    Commented Feb 25, 2021 at 16:02
  • @trentcl I can almost see where you're coming from there, but it seems a stretch. The phrase "integer is a type" has a widely recognised meaning in both static and dynamic typing systems, as does the phrase "integer is a sub-type of number". It also doesn't explain the distinction Jörg is making between classes and types, since there are plenty of languages where class constraints are analysed statically rather than dynamically. As I said elsewhere, the distinction might be useful, but it's not one that's commonly made, so I'm still waiting for it to be explained explicitly.
    – IMSoP
    Commented Feb 25, 2021 at 16:28
4

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!
2

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.

2
  • Thats interesting! I've had little to no exposure to Hack before, but I'll give it a closer look, thanks. As far as static methods on interfaces, given that you say 'most' and 'generally', are there any that do support it and treat it that way? In such a case where an interface could have statics and be passed as an argument, I imagine an implementation could exist where an object could also be constructed from it by treating a new or equivalent call as just a static function that returns object of that type anyway
    – AIWalker
    Commented Feb 25, 2021 at 3:18
  • @AIWalker - Ehhh, I know of some languages that "dispatch" static methods, but as a sort of mistake. The type just happens to have a method with that name so it's executed. It was a problem because there was no guarantees that the method it found had a compatible signature. I've tried to purge it from my memory, so don't remember which ones actually suffered from it.
    – Telastyn
    Commented Feb 25, 2021 at 3:54
2

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.

2
  • The OP is asking about types, not classes. Classes aren't types in Smalltalk, protocols are, and they only exist in the programmer's head. (Or in more modern Smalltalks, they are used for categorizing methods in the IDE, but they are still "just" documentation, not part of the language.) Commented Feb 25, 2021 at 7:30
  • @JörgWMittag The OP explicitly talks about classes in the question, and gives the example of dynamically calling appropriate constructors. The definition of "type" you're using may be useful, but it's not the one used in the question.
    – IMSoP
    Commented Feb 25, 2021 at 10:19
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.