I have an object in the following structure:
const geo = {
europe: {
germany: ['berlin', 'hamburg', 'cologne'],
france: ['toulouse', 'paris', 'limoges'],
italy: ['rome', 'venice', 'genoa'],
},
asia: {
india: ['mumbai', 'rajkot', 'pune'],
china: ['shenzhen', 'beijing', 'shanghai'],
nepal: ['kohalpur', 'ghorahi', 'hetauda'],
},
};
And I want to write a simple function that randomly selects a city, given continent and country. Although it seems to me that a straightforward implementation would be
const randomElement = (arr) => arr[Math.floor(Math.random() * arr.length)];
const pickCity = (continent, country) => randomElement(geo[continent][country])
// calling `pickCity()`
pickCity("europe", "france") // => we get one city at random, as requested
I nevertheless wonder whether such implementation is falling under the category of a "stringly typed" function, which is said to be a sub-optimal practice.
On the other hand, if I attempt to avoid those string parameters, then the alternative would be more verbose and not respecting the DRY principle:
const pickCityGermany = () => randomElement(geo.europe.germany)
const pickCityFrance = () => randomElement(geo.europe.france)
const pickCityItaly = () => randomElement(geo.europe.italy)
const pickCityIndia = () => randomElement(geo.asia.india)
const pickCityChina = () => randomElement(geo.asia.china)
const pickCityNepal = () => randomElement(geo.asia.nepal)
Is pickCity()
indeed "stringly typed", or am I misinterpreting the concept?
UPDATE
Following @Bergie's answer below, I've realized that TypeScript's enum
is likely a proper solution to typing pickCity()
's parameters. So I took a stub at this, but see below, this is still not quite a complete solution.
I started with creating two enum
s, one for continent and one for country:
// typescript
enum Continent {
Europe = "europe",
Asia = "asia"
}
enum Country {
Germany = "germany",
France = "france",
Italy = "italy",
India = "india",
China = "china",
Nepal = "nepal"
}
However, as @Bergi wrote, they are dependent on each other because the way geo
data is structured. So the only way I could solve it right now is by typing any
, which is certainly not a solution I want:
type Geo = Record<Continent, any> // <~-~-~-~-~ `any` is :(
const geo: Geo = {
europe: {
germany: ['berlin', 'hamburg', 'cologne'],
france: ['toulouse', 'paris', 'limoges'],
italy: ['rome', 'venice', 'genoa'],
},
asia: {
india: ['mumbai', 'rajkot', 'pune'],
china: ['shenzhen', 'beijing', 'shanghai'],
nepal: ['kohalpur', 'ghorahi', 'hetauda'],
},
};
const randomElement = (arr) => arr[Math.floor(Math.random() * arr.length)];
const pickCity2 = (continent: Continent, country: Country) => randomElement(geo[continent][country])
// calling pickCity2()
console.log(pickCity2(Continent.Europe, Country.Germany)) // => one German city at random
why can't we simply untangle the dependency by changing geo
?
@Bergi asked this, because it's seems like an unneeded complication. So had we have the following geoSimpler
instead, life would have been easier:
type GeoSimpler = Record<Country, string[]>;
const geoSimpler: GeoSimpler = {
germany: ['berlin', 'hamburg', 'cologne'],
france: ['toulouse', 'paris', 'limoges'],
italy: ['rome', 'venice', 'genoa'],
india: ['mumbai', 'rajkot', 'pune'],
china: ['shenzhen', 'beijing', 'shanghai'],
nepal: ['kohalpur', 'ghorahi', 'hetauda'],
};
const pickCity3 = (country: Country) => randomElement(geoSimpler[country])
console.log(pickCity3(Country.France)) //
However!
I still want to account for data structures that could not be simplified. For example, consider geoTwinCitiesAttractions
:
const geoTwinCitiesAttractions = {
us: {
cairo: ['U.S. Custom House', 'Cairo Public LIbrary', 'Magnolia Manor'],
memphis: ['Graceland', 'National Civil Rights Museum', 'Beale Street'],
saintPetersburg: ['The Dali Museum', 'Sunken Gardens', 'Chihuly Collection'],
moscow: [],
athens: ['Pleasant Hill Vineyards', 'Little Fish Brewing', 'Athens Farmers Market'],
},
greece: {
athens: ['Acropolis of Athens', 'Parthenon', 'Mount Lycabettus'],
},
france: {
paris: ['Eiffel Tower', 'Louvre Museum', 'Arc de Triomphe'],
},
russia: {
saintPetersburg: ['State Hermitage Museum', 'Savior on the Spilled Blood', 'Winter Palace',
],
moscow: ['Red Square', 'Bolshoi Theatre', 'Cathedral of Christ the Saviour'],
},
egypt: {
cairo: ['Giza Necropolis', 'Mosque of Muhammad Ali', 'Salah Al-Din Al-Ayoubi Castle'],
memphis: ['foo', 'bar', 'baz'],
},
};
Here we must specify both country and city, because there are same-name cities in different countries. So how could we use enum
to avoid the "stringly typed" pickAttraction()
:
const pickAttraction = (country: string, city: string) => randomElement(geoTwinCitiesAttractions[country][city])