A little over one year ago, I posted this short thought to twitter:
While I’ve largely abandoned my work on the physics of software, a few days ago I stumbled on some notes and I somehow felt a small push to write down a little on this subject. No big deal, but perhaps some of you will find it interesting.
Most languages have a notion of identifiers; in the common conceptual framework of denotational semantics, identifiers are syntactic elements, and they denote something[*] in the semantic world. That denotation is said to give meaning to the identifier. [*] “something” can be a value, but also a function, a procedure, etc.
Most languages also have a notion of scope. The scope is again a syntactic notion, through which a specific binding from identifiers to value takes place. This is commonly modeled in denotation semantics through the notion of environment.
What is the notion of scope actually accomplishing? It’s creating a way to make meaning local, in this case local within a scope. [I won’t go into the different ways one can define locality and distance; it would take too much and I think the ideas here can be followed without that piece of the puzzle.]
Late binding (dynamic binding), usually experienced these days through inheritance + polymorphism, allows (e.g.) a derived class to create a new locus of meaning. Note that in this case the new locus includes all the parent classes, when the target object is of a specific derived class. It’s through this ability to create locality of meaning that run-time polymorphism (late binding in general) becomes useful.
Meaning is not restricted to values. Generic types have a denotation as well, and some languages allow once again to locally redefine a generic type, where locality is not defined by scoping but by type specialization. Consider good old C++:
template< typename T > class Vector { … } ;
That would model a Vector of T, for any type T. Unlike many other languages, C++ allows partial and total specialization on T, so you could say:
template<> class Vector<bool> { … } ;
And define an optimized version of Vector when T is bool, or
Template< typename T > class Vector< T* > { … }
And define an ad-hoc version of Vector when a pointer type is involved, etc.
There are somewhat complicated rules to choose the “best option” when Vector<Something> is used, which can really be seen as finding the specialization that is closer to Something, given the right notion of distance. Again, however, it is in its ability to define new loci of meaning that template specialization gets most of its power.
In most imperative languages, the sequencing of operations is specified as part of the denotational semantics, but is a language-level choice that cannot be altered. The meaning of syntactical sequencing is sequential execution. That is not true in functional, lazy languages, where evaluation is carried out on demand, and order is determined by needs. Still, other parts are usually hard-coded in functional languages, like the meaning of function composition. Monads get their real power because they allow the programmer to define a new locus[*] of meaning for sequencing, providing local semantics for what it means to chain operations together. [*] Note how the vernacular "lifting into the monad" implicitly recognizes the locus.
Every paradigm / language gets power from constructs that allow to create locality of meaning (not just for identifiers, as we have just seen).
Consider Aspect Orientation. A pointcut implies a binary distance measure - either you match with the pointcut or not. An advice provides redefined meaning for a number of things within the locus falling inside a pointcut.
Consider DCI. The entire point of DCI is that the Context defines the available methods on the objects[*] - once they get inside the context (the context being, of course, our locus of meaning for the objects). [*] Yes, it’s the context not the class.
Why is this worth understanding? Two reasons:
- when you see something powerful in a language, if it fits within this notion, you gain an interesting perspective by understanding in depth what is the distance at play, and which kind of meaning is possible to [re]define locally.
- as one of the central themes in the Physics of Software, a solid understanding of this notion, even better if backed up by an extensive review of existing programming languages, can lead to better material engineering, that is, better programming language design, less focused on grandiose thinking and more focused on which reactions we want from our artifacts when a certain stimulus is applied.
Of course, that’s the missing part in this post: why are all of the above useful? Which stimuli do the handle well, and how? Questions to which I can only answer by copying here another tweet of mine:
Most languages have a notion of identifiers; in the common conceptual framework of denotational semantics, identifiers are syntactic elements, and they denote something[*] in the semantic world. That denotation is said to give meaning to the identifier. [*] “something” can be a value, but also a function, a procedure, etc.
Most languages also have a notion of scope. The scope is again a syntactic notion, through which a specific binding from identifiers to value takes place. This is commonly modeled in denotation semantics through the notion of environment.
What is the notion of scope actually accomplishing? It’s creating a way to make meaning local, in this case local within a scope. [I won’t go into the different ways one can define locality and distance; it would take too much and I think the ideas here can be followed without that piece of the puzzle.]
Late binding (dynamic binding), usually experienced these days through inheritance + polymorphism, allows (e.g.) a derived class to create a new locus of meaning. Note that in this case the new locus includes all the parent classes, when the target object is of a specific derived class. It’s through this ability to create locality of meaning that run-time polymorphism (late binding in general) becomes useful.
Meaning is not restricted to values. Generic types have a denotation as well, and some languages allow once again to locally redefine a generic type, where locality is not defined by scoping but by type specialization. Consider good old C++:
template< typename T > class Vector { … } ;
That would model a Vector of T, for any type T. Unlike many other languages, C++ allows partial and total specialization on T, so you could say:
template<> class Vector<bool> { … } ;
And define an optimized version of Vector when T is bool, or
Template< typename T > class Vector< T* > { … }
And define an ad-hoc version of Vector when a pointer type is involved, etc.
There are somewhat complicated rules to choose the “best option” when Vector<Something> is used, which can really be seen as finding the specialization that is closer to Something, given the right notion of distance. Again, however, it is in its ability to define new loci of meaning that template specialization gets most of its power.
In most imperative languages, the sequencing of operations is specified as part of the denotational semantics, but is a language-level choice that cannot be altered. The meaning of syntactical sequencing is sequential execution. That is not true in functional, lazy languages, where evaluation is carried out on demand, and order is determined by needs. Still, other parts are usually hard-coded in functional languages, like the meaning of function composition. Monads get their real power because they allow the programmer to define a new locus[*] of meaning for sequencing, providing local semantics for what it means to chain operations together. [*] Note how the vernacular "lifting into the monad" implicitly recognizes the locus.
Every paradigm / language gets power from constructs that allow to create locality of meaning (not just for identifiers, as we have just seen).
Consider Aspect Orientation. A pointcut implies a binary distance measure - either you match with the pointcut or not. An advice provides redefined meaning for a number of things within the locus falling inside a pointcut.
Consider DCI. The entire point of DCI is that the Context defines the available methods on the objects[*] - once they get inside the context (the context being, of course, our locus of meaning for the objects). [*] Yes, it’s the context not the class.
Why is this worth understanding? Two reasons:
- when you see something powerful in a language, if it fits within this notion, you gain an interesting perspective by understanding in depth what is the distance at play, and which kind of meaning is possible to [re]define locally.
- as one of the central themes in the Physics of Software, a solid understanding of this notion, even better if backed up by an extensive review of existing programming languages, can lead to better material engineering, that is, better programming language design, less focused on grandiose thinking and more focused on which reactions we want from our artifacts when a certain stimulus is applied.
Of course, that’s the missing part in this post: why are all of the above useful? Which stimuli do the handle well, and how? Questions to which I can only answer by copying here another tweet of mine:
So, go make it a better world, thanks!