First Class

A language construct is said to be a FirstClass value in that language when there are no restrictions on how it can be created and used: when the construct can be treated as a value without restrictions.

FirstClass features can be stored in variables, passed as arguments to functions, created within functions and returned from functions. In dynamically typed languages, a FirstClass feature can also have its type examined at run-time.

Languages vary as to what is FirstClass. Some, such as the CeeLanguage have only basic types (ints, pointers; in particular, arrays are not truly FirstClass though the array/pointer equivalence lets you fake it in most situations). In ObjectOriented languages such as CeePlusPlus, objects are FirstClass but classes are not, while in SmalltalkLanguage or RubyLanguage, for example, all references including references to class objects are FirstClass. In FunctionalProgramming, functions are FirstClass.

Some authors, such as Raphael A. Finkel in AdvancedProgrammingLanguageDesign, also define the terms SecondClass and ThirdClass? (pg76); different authors have varying definitions of exactly what properties things should have to be considered first, second, or third class, but the terms are typically broadly similar to Finkel's.

                                |      Class of value
    Manipulation                | First    Second    Third
 Pass value as a parameter      | yes      yes       no
 Return value from a procedure  | yes      no        no
 Assign value into a variable   | yes      no        no

I think all attempts to define other classes that the FirstClass are bound to fail, because different things that you can't do depend on the language paradigm. FirstClass depicts something relatively clear-cut in every paradigm: the full-fledged ability to be a value. -- PanuKalliokoski I.e. you can do "anything" (treat it in all ways like any other value) with a FirstClass construct, nothing value-like with a ThirdClass? construct, and a little bit with a SecondClass construct (in Finkel's terminology, only pass as a parameter).

He gives as example that procedures (qua procedures: closures or function pointers) are ThirdClass? values in Ada, SecondClass values in Pascal, and FirstClass values in C and Modula-2.

This also shows shortcomings in this terminology; you can treat function pointers as values in C, but the function pointer isn't a closure, so FirstClass functions in C are sharply less expressive than FirstClass functions in e.g. Lisp.

If one wants more complex classification of the NthClassness? of different programming concepts, one should take into account at least the following: ability to pass, return, store, compare, construct, type-identify and destructure (inspect) the value at RunTime. For example, C functions lack the abilities to construct and destructure. -- PanuKalliokoski The problem is not so much with the term "FirstClass" as it is that the term "function" doesn't mean quite the same thing in every language.

The question may arise, what does it mean for something to be ThirdClass?, if you can't do "anything" with it? That means you cannot treat it as a value, but it has other uses. For instance, types in C such as int are ThirdClass?; they cannot be used as values in any context at all, but obviously are necessary for declarations.

ThirdClass? types implies no TypePolymorphism?, which historically has been the AchillesHeel of strongly typed languages. For instance, Pascal originally was crippled in regard to things like string manipulation, because strings are arrays of characters, but Pascal did not allow polymorphism on the array length in user-defined functions.

C++ and Ada implement limited TypePolymorphism? by allowing modules and procedures to accept type parameters (generic modules, generic procedures), creating a general template and then specializing it (at compile time) for a particular type.

CeeLanguage PointerCastPolymorphism is a traditional way to implement TypePolymorphism? under programmer (rather than language) control, which, although error-prone, allowed manipulations in C that were literally impossible in more "strongly-typed" languages such as Pascal.

This has lead to a great deal of confusion and HolyWars right up to the present, where advocates of strict static strong typing tend to view escapes from strong typing as an obvious morally wrong thing that leads to bugs, and that therefore C programmers are a bunch of yahoo cowboys who can't be bothered to self-discipline themselves enough to switch to a language like Pascal to avoid such an obvious source of bugs - while C programmers have been baffled that programmers would advocate giving up something as powerful as TypePolymorphism? (even though it has to be hand-managed via PointerCastPolymorphism) merely because it can be a source of bugs, since isn't it better to be able to write a program that might have bugs, than to not be able to write it at all? So therefore I come to this conclusion: BondageAndDisciplineLanguages? (strict, statically typed) that do not provide FirstClass types for TypePolymorphism? are evil, when they've been telling us they were good for us all these years! :-) Meanwhile, dynamically typed languages like Lisp have just been watching all this with bemusement for half a century...

-- DougMerritt

There's the middle path: MlLanguage's offspring with TypeInference and ParametricPolymorphism.

Yes indeed, but commits a related sin. As discussed recently on OcamlTypeSafetyProblem, ML-family languages do in fact need a dynamic type for some things, yet (a) that feature is still lacking, and (b) a need for it is widely (although not universally) vehemently denied by many ML-family fans - see e.g. recent Lambda The Ultimate discussions.

The wider point is that it has been common (well, since 1954 or so) for language designers to ban feature X in order to provide feature Y (such as an improvement or guarantee of absence of a certain kind of programmer bug), claiming "Y is important, and anyone who uses X is a bad person, anyway; you don't need that!". Somehow such claims have generally been extremely persuasive - yet ultimately wrong. Yes, Y is important (type safety in this case), but required throwing out both dynamic typing and polymorphism.

This should be a matter of common sense, but so long as major ML dialects lack dynamic, we've still got major problems on our hands.

The situation is much worse in other mainstream languages. Most languages outside of functional languages have been very slow to admit the need for polymorphism. C++ templates are, of course, far better than not being able to do generic programming - except the cure is almost worse than the disease. C# and Java are adding their own variants of C++ templates, promising they will be "better", but this seems dubious. The real problem is that they're desperately avoiding FirstClass types!

Lisp got that part right, but went wrong in insisting that there's no value in static typing; the BondageAndDiscipline languages are correct that type safety is desirable - compile time rather than run time errors - if sacrifices to that god aren't required.

And meanwhile scorn continues to be heaped on poor little C, because it uses an error-prone old-fashioned approach to polymorphism, rather than using the shiny modern but broken template approach.

Poor thing. C has obvious problems, but in a way, was the only language to get it right. We're still waiting for the ultimate replacement for C, and instead what we get are empty promises.

The ML camp is unlikely to deliver on their potential any time soon; they insist on calling dynamically typed languages "untyped". That reflects a very deep and unjustified prejudice that inherently prevents movement in the correct direction.

C#'s reflection library does actually provide first-class types. Through the reflection library, types can be constructed, fetched, manipulated, etc. The TypeInfo? object has everything you need to do whatever dynamic dispatch you like. The catch is that to do this you'll have to do a lot of cruel and dangerous casting, fetching bizarre objects from dictionaries, and generallly handling the land of dynamic typing with the kid gloves that a static language is supposed to use in such cases.

The fact is that run-time constructable types pretty much requires a Python-style "member access by dictionaries" approach which is (a) horribly slow, and (b) completely removes compile-time checking. Otherwise, how do you provide the methods? How do they know what's in their namespaces if they are defined before the object is created?

Personally, I still think that the best approach for statically-typed languages is a hyper-powerful metaprogramming mechanism. At some point you have to suck it up and admit that code generators are necessary for compile-time programming and settle into the nitty gritty of making them better than the C preprocessor or the C++ templates.

Just say yes to FirstClass types!

Just because you fly FirstClass doesn't mean you will get VeryGoodSeats.

EditText of this page (last edited January 25, 2006) or FindPage with title or text search