

Type System Differences in TypeScript (Structural Type System) VS C# & Java...
source link: https://www.triplet.fi/blog/type-system-differences-in-typescript-structural-type-system-vs-c-java-nominal-type-system/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Type System Differences in TypeScript (Structural Type System) VS C# & Java (Nominal Type System)
October 15, 2017 | typescript
When a developer with C# or Java background learns TypeScript, there is a temptation to write TypeScript with the same style as C# or Java. It is easy to lose TypeScript's beauty by not understanding the type system TypeScript offers and how it differs from languages that are already familiar to the developer. One of the differences is that TypeScript uses Structural Subtyping. C# and Java both use Nominal type system. In this blog post, I'll cover basics of both systems and few examples, enjoy!
Nomen est omen
Nominal refers to Latin word nomen, the name. Many might be familiar with phrase nomen est omen (the name is a sign) or nomen est omnis (the name is everything) and the especially the latter phrase can be applied to programming context also. The name of the type defines is the type compatible or not, for example, as a method argument.
Let's look at a C# example. 3rd party UI library contains a drop-down component and it will take a list of options.
public class SpecialOption
{
public string Name { get; set; }
public int Id { get; set;}
}
The user of the library wants to display a list of companies and the backend already has a service that returns a list of company names together with ids.
public class Company
{
public string Name { get; set; }
public int Id { get; set; }
}
public class CompanyService
{
public IEnumerable<Company> GetAll() {
...
}
}
Our class Company and SpecialOption are identical, but unfortunately, list of companies are not compatible as they are considered different types.
There are several options to fix this:
- make Company and SpecialOption compatible by making them implement the same interface
- make Company and SpecialOption compatible by making them inherit the same base class
- loop all elements and map Company to SpecialOption
Unfortunately, only the latter is a valid option as 3rd party libraries API interfaces and types cannot be changed.
Structural typing
In TypeScript, the interface tells what is the structure that is accepted. The previous example could be written like this.
interface Company {
name: string
id: number
}
interface SpecialOption {
name: string
id: number
}
const companies:ReadonlyArray<Company> = [{
id: 1,
name: 'AcmeCorp'
}]
const DropdownList = ({ options }:{ options: ReadonlyArray<Company>}) => {
}
DropdownList({
options: companies // <- no errors as the structure matches, name doesn't matter
})
The TypeScript example has a bit more lines because I made a mock UI control + initialization. The main thing is that companies are valid options. If I change a property name on the Company to the title, there will be an error, so I still have type-safety.
A bigger real-world example could be from the React app.
Let's imagine a scenario where I need to support different modes (read/edit) for each type of component. Each component should have read/edit modes defined.
Photos -> View -> Edit Maps -> View -> Edit Feedback -> Edit <- should give a compile-time error as the View is not implemented ...place dozen components here, etc.
One way to model the solution is to use structural typing.
import ViewComponents from './View'
import EditComponents from './Edit'
const renderers = {
[RenderType.View]: ViewComponents,
[RenderType.Edit]: EditComponents
}
const render = (
data: FormModule,
renderType: RenderType
): JSX.Element => {
const renderer = renderers[renderType]
switch (data.type) {
case ComponentType.Photos:
return <renderer.Photos data={data} />
case ComponentType.Maps:
return <renderer.Maps data={data} />
default:
throw new Error('Not implemented')
}
}
The idea is that when a developer implements new component then she/he needs to implement both View and Edit, otherwise there will be an error.
How is this done without any interfaces?
These two lines need to import objects that have the same structure:
import ViewComponents from './View'
import EditComponents from './Edit'
In my case, View and Edit export the right components.
View folder -> index.tsx
import Photos from './Photos'
import Maps from './Maps'
export default {
photos: Photos,
maps: Maps
}
---
Edit folder -> index.tsx
import Photos from './Photos'
import Maps from './Maps'
export default {
photos: Photos,
maps: Maps
}
The renderer structure needs to be same and then the component can be rendered:
const renderer = renderers[renderType]
return <renderer.Photos data={data} /> // <- there would be error if Photos would not be in either View or Edit renderer
--
this is how the renderers data looks:
renderers: {
view: {
Photos: PhotosViewComponent,
Maps: MapsViewComponent
},
edit: {
Photos: PhotosEditComponent,
Maps: MapsEditComponent
}
}
Conclusion
I hope the provided examples help understand the difference between nominal and structural typing. Maybe in your codebase, there are unnecessary complex mappings or inheritance that could be simplified using some benefits that structural typing brings.
Nominal typing has its own merits and as the Wikipedia page of nominal typing points out, there are some costs:
Nominal typing is useful at preventing accidental type equivalence, which allows better type-safety than structural typing. The cost is a reduced flexibility, as, for example, nominal typing does not allow new super-types to be created without modification of the existing subtypes.
It's our duty as developers to understand pros and cons of each approach. To make proper use of pros and understand cons leads to elegant solutions. Happy coding!

Hi there! I'm Tatu, an independent Web developer; I help clients on building successful SaaS products. If you're interested in freelancing, you should check my email list How to get started as a freelancer. In my email list, I share private learnings from my own journey, including information about pricing, finding clients, working with the clients, how to get started, etc.
Please don't hesitate to reach out by sending an email or via Twitter.
Recommend
-
59
The goal of a programming language’s type system is to provide guarantees about what types can be encountered at certain points in your program; to document the values we expect to see in a way that the compiler can veri...
-
16
TypeScript is a superset of JavaScript. Any JavaScript code is a valid TypeScript code, as long we set the compiler not to be strict. Therefore, TypeScript aims to be as flexible as possible so that it can apply to various...
-
8
What are the differences between type() and isinstance()? What are the differences between these two code fragments? Using type():...
-
6
Seeing system dynamics in organizational change: 6) the scope of structural change By Lv Yi on August 26...
-
8
Seeing system dynamics in organizational change: 7) incremental structural change By Lv Yi on August 30,...
-
5
-
10
Growth & MarketingMay 17, 2022KYC & KYB processes: Meaning, differences, and key conceptsAndrea L. LozanoContent SpecialistIn an in...
-
10
Extreme Explorations of TypeScript's Type SystemJune 27, 2022 · 6 min readTypeScript's type sys...
-
11
Differences in Code Sharing Between Microservices & MicrofrontendsThey differ fundamentally when it comes to code sharing. Get to know why
-
7
AI system can generate novel proteins that meet structural design targets These tunabl...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK