Domain and codomain

If you remember math class in high school, then you should be familiar with mathematical function definitions like f(x): y = x + 2. In this example, the function gives you an y value, given an x value.

Suppose you wouldn’t know how the function works, that it would be a black box: y = f(x). There might be multiple ways the function could be defined. One of them is the formula above. But the function can also be regarded as a mapping between x values and y values.

Read more: Domain and codomain

In mathematical terms, a function can be seen as a mapping between a domain and a codomain. But what are these exactly? The domain can be seen as the set of all possible input values. The codomain is then the set of all possible output values.

There are a few aspects to consider in this definition. Someone might come up with mappings that map several values in the domain to the same value in the codomain (this happens with the sine function for example). That implies that it is not always possible to go from codomain back to the orginal domain. The mathematical function represents a one-way mapping.

How does it work in code?

When we apply this to software engineering, we could also define functions as mappings between a domain and a codomain. We are also not restricted to numbers, a domain or codomain can be defined by any type. However, be aware that the domain or codomain notion are not identical to a type definition. Take for example the Math.sqrt() function: although both the domain (the input parameter) and the codomain (the output value) are defined as double, this still does not mean that all possible double values are valid input values: only values ≥ 0 are accepted. Or take Math.sin(): it accepts any double as input, but the output value is always between -1 and 1.

But if we’re not limited to numbers, we can also use arbitrary types. We can map x and y coordinates to Point instances for example. In this regard, a constructor can also be seen as a function that maps from a domain to a codomain.

Mappings like these occur often in source code, so it can be useful to be aware of the properties of such a mapping. Some practical examples:

  • We can map x and y coordinates to Point objects. Then we can reason about coordinates in the 2D space so we can do things like applying offsets and scaling. It would be cumbersome to apply operations like these on the separate x and y coordinates everywhere.
  • We can map objects to lists of objects, and then we can perform operations on the list.
  • We can map actions to the delayed evaluation domain: this is what the Task class does. Instead of running a function and continuing after it has completed, we can schedule it for execution in some other moment.
  • Or we can implement it in a pull-based manner, like IEnumerable does.
  • Or, in a graphical application, we could wrap a graphics handle in an object. Then we can do interesting things with the object, knowing that the handle is always valid.
  • Password encryption: it maps from the plain text password domain (all valid passwords) to the encrypted password domain. If you want good security, then you preferably want an encryption system that makes it very hard to go back from encrypted passwords to plain text.

If you go from a domain to some codomain, then you basically enter a different world, which may have different rules. For instance, take the graphical application example. When all operations were defined in terms of handles, then any handle would do. But when you wrap it into a graphics object, then you basically give the message: use this handle whenever you need, but know that this handle is a valid one. You don’t have to check the handle on every interaction. But also: the application may become much easier to understand when it is described in terms of graphics objects instead of handles.

In a good design, you preferably want to go one way. You go from coordinates to Points, from individual objects to lists, from actions to Tasks.

Why is this? If you stay in the codomain, then you don´t have to get original values back, which would break encapsulation. The exception would be the boundary of the software system. Also, in the codomain, you can define operations that are well-suited to solve the problem at hand, because it encapsulates the correct concept.

Leave a Reply

Your email address will not be published. Required fields are marked *