1

Progressive enhancement usually refers to web content, and the idea of having a page that is readable before it's fully functional.

I'm suddenly preoccupied with the idea that I can't progressively enhance my code. I want to be able to add methods to my values. I might have a string property. If I want to enhance it with an IsValid() method, I've no way of doing that without changing the caller.

On one hand, this seems inevitable. I used to have a string. Now I've got an object with a string "value" and some methods. It's a totally different type, and I have to call it differently.

But, the reality I'm modelling hasn't changed. I've just gotten around to adding IsValid(). Why should existing callers who just want the value (and don't care about validity) have to adapt? C# has the notion of default properties, where, you can address a collection property just by providing the containing class and the index. So you could change a List into SomeClassWithAListInside without the caller caring. Why can there be no equivalent for other types of properties? Do other languages deal with this differently?

5
  • You can provide a default isValid implementation, but realistically it couldn't do anything but return true always. And callers would quickly learn that the method is useless because it doesn't add information. How are you going to notify (and convince) all your callers when the method suddenly does have value? Feb 3, 2017 at 11:15
  • What is a "valid" string? What you're talking about isn't progressive enhancement, it's putting domain logic into primitive types. This often feels like an elegant solution at first but spirals completely out of control at scale. You don't want string.IsValid, you want DomainType.IsValid. A language with very lightweight type alias syntax and typeclasses would be perfect for that use case, but you can do it anywhere.
    – Phoshi
    Feb 3, 2017 at 12:01
  • @Killian Foth Ok thanks for your interest, but I didn't get my point across. I want to be able to change my primitive types, (or perhaps complex types and system types), into domain objects, while keeping the old interface for callers that expect that. How can I make the question clearer?
    – bbsimonbb
    Feb 3, 2017 at 13:03
  • @Phoshi for a concrete example, the ConnectionString type in C#, I seem to remember, used to be a string. Then it got some other properties, like providerName, so the actual string is now available at ConnectionString.value. All calls have to be changed, and folk who don't know or care about providerName are going to find the syntax non-obvious. I could come up with dozens of similar examples. My IsValid() is not an extension method for all strings. Its a string mutating into a domain object, which I'd like to do without it losing its stringiness.
    – bbsimonbb
    Feb 3, 2017 at 13:20
  • The original design for ConnectionString was a mistake for that reason, and they corrected that mistake by making the domain value a domain type. Strings can contain anything, and if you add "IsValid" to them which actually does something domain-specific then you're exposing a very specific interface to the entire world. It's not likely to be what you want.
    – Phoshi
    Feb 3, 2017 at 15:18

3 Answers 3

2

You can add new methods to all classes which you control, and if you don't control a class, you can provide a subclass which support the new methods, and this will be transparent to any caller.

So the issue you describe is only an issue if you want to extend a class which you don't control and which is sealed so it cant be subclassed - like String in some languages. String and primitives are sealed as a compromise for performance in these languages, so either you should change to a more flexible language, or you should avoid exposing domain objects in the form of primitives in the first place.

Some dynamic languages like Python and JavaScript allows you to extend arbitrary objects with new methods, which is called "monkey patching". This kind of solves your issue, but is generally a bad idea because you risk clashes, and because when you turn say a string into a domain object, you will turn every string in the program into this domain object which is obviously crazy. Extension methods in C# is a different approach which avoids clashes, but still have the fundamental problem that is will apply to all instances of the extended type.

3
  • 1
    Of course. How did I get myself into a lather.
    – bbsimonbb
    Feb 3, 2017 at 14:03
  • 1
    "String and primitives are sealed as a compromise for performance in these languages" It's not just a matter of performance. It's also a matter of security and correctness. You could subvert all sorts of security check if you could provide your own String subclasses that don't play by the rules. As for correctness, you should only extend classes that are actually designed to be extended, or you'll run afoul of the Fragile Base Class problem.
    – Doval
    Feb 3, 2017 at 19:32
  • You don't need to use monkey patching (generally understand as modifying the system class/prototype object with new features) to achieve this; you can apply new features to objects on a one-by-one basis as well. See my answer for an example. Feb 4, 2017 at 23:58
2

Procedural languages deal with this very simply. When you want more functionality, you just add an isValid(String) function. The callers that didn't know about this function don't use it. The isValid function doesn't have to be physically located together with other String functions.

In functional languages, you can take advantage of immutability to make as many copies as you want of data. Some of those copies can have transformations applied or other data associated with it in a data structure, and you know it stays in sync because of immutability. This means you can have some code working with a String and some working with an EnhancedString, but they're both working on the same underlying value.

Encapsulating all your mutability behind object boundaries has advantages, but it also has limitations, and you've stumbled across one of the big ones.

0

Many dynamically-typed languages will allow you to do this kind of thing quite easily. For example, in Javascript, you could have something like this:

var myString = new String("hello, world");
var myOtherString = new String("goodbye everything");
function stringValidator ()
{
    return this.indexOf(',') >= 0;
}
myString.isValid = stringValidator;
myOtherString.isValid = stringValidator;

alert (myString.isValid () + ", " + myOtherString.isValid()); // result: "true, false"

More generally, your problem relates only to static typing. If you have two different types that you may expect an item to have, in a dynamically-typed language it is always possible for you to return an object that has both types at the same time. A static language may or may not allow for this (some can do so via multiple inheritance, e.g. in C++ you could implement a class that extends the standard string class and also adds additional features, but in C# or Java you can't because the string classes in those languages are sealed and cannot be extended).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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