Type | Treat 2021 – Day 4

Type | Treat Challenge 4

Welcome to the fourth Type | Treat challenge! These challenges are a series of blog posts which have 2 code challenges in, one for beginners and one for intermediate TypeScript programmers. We’re on day four, which m…


This content originally appeared on DEV Community and was authored by Orta

Type | Treat Challenge 4

Welcome to the fourth Type | Treat challenge! These challenges are a series of blog posts which have 2 code challenges in, one for beginners and one for intermediate TypeScript programmers. We're on day four, which means going over the answers from yesterday and have 2 new challenges.

Yesterday's Solution

Beginner/Learner Challenge

I wonder if we over-indexed on the difficulty here, and we're interested if you dropped off somewhere through this task because we had less submissions than usual for this challenge. The goal was to have you build out a template string literal type which accounted for string input which roughly matched how CSS's stringy variables worked.

You started with:

type Length = string

Which accepts all possible strings, next we show some examples which should always fail. The key one here being that an empty string should fail: "". Next we provided some valid input for you to work with:

type Length = `${number}in`

// Works with:
req("0in")
req("12in")

Giving you a sense that a number can be used in the template slot - which allows for all sorts of possibilities.

Next we gave samples with different prefixes, so "in" and "cm" would need to be handled. To get that right, you would need to use a union:

type Unit = "cm" | "in"
type Length = `${number}${Unit}`

// Works with:
req("0in")
req("12in")
req("1.5cm")
req("20cm")

Next we threw a curve ball - "0" should also be acceptable, this is a bit of a curve ball, but also it's a bit of a trick:

type Unit = "cm" | "in" | ""
type Length = `${number}${Unit}`

// Works with:
req("0in")
req("12in")
req("1.5cm")
req("20cm")
req("0")

The lack of a unit is just an empty string unit! Only one more thing now, and that is allowing a space inbetween the number and unit. This could be done via another type also:

type Unit = "cm" | "in" | ""
type Space = " " | ""
type Length = `${number}${Space}${Unit}`

// Works with:
req("0in")
req("12in")
req("1.5cm")
req("20cm")
req("0")
req("12 cm")
req("14 in")

That was is for the easy parts of the challenge. It's pretty tricky, because it requires that you understand that number can be anything in the template string and to understand how a union can allow for different types of strings inside the type. That's all in the main docs, but it could be a lot of ideas to learn at once.

This challenge also had a set of complications, cases where the version of the the Length type we expected people to build would provide interesting edge cases:

req(`${0.3e21}cm`)
req("-12 cm")
req(`${Infinity}cm`)
req(`${NaN}cm`)

Click to learn about these cases

req(`${0.3e21}cm`)

Acted as a potential clue to an alternative answer for these failing cases:

req(`${Infinity}cm`)
req(`${NaN}cm`)

Because number can be switched out with bigint in the type of Length:

- type Length = `${number}${Space}${Unit}`
+ type Length = `${bigint}${Space}${Unit}`

This meant you couldn't pass in Infinity or NaN but also broke req("1.5cm") because you can't have point values. This could be fixed via:

type Length = `${bigint}${Space}${Unit}` | `${bigint}.${bigint}${Space}${Unit}`

Which describes both possible cases with a "." and without. This technique still doesn't handle the req("-12 cm"), and actually, it introduces a whole new problem: req("-12.-12cm") is allowed!

We spotted a good answer from @danvdk which revolved around using string manipulation instead, by introducing a Digit type:

type Whitespace = '' | ' ';
type Unit = 'in' | 'cm';
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
type Length = `${Digit}${number | ''}${Whitespace}${Unit}` | '0';

This solution correctly handles the case of req("-12 cm") but via that number would allow something like req("1-22 cm") - which you can pretend is to handle an input range. It wouldn't be hard to take this solution and reasonably cover additional edge cases. Very cool solution.

Our answer

Intermediate/Advanced Challenge

The intermediate challenge was on type literals mixed with generics functions. The challenge started with this function:

function makeTitle(str: string) {
    return "<spooky>" + str.toUpperCase() + "</spooky>"
}

The goal was to keep track of string literals through this function. To do this, you need to switch the str: string to be a type argument:

function makeTitle<Str>(str: Str) {
    return "<spooky>" + str.toUpperCase() + "</spooky>"
}

You know that the type argument has to be a string, which you can tell TypeScript via <Str extends string>, then you can re-use the Str in the return position:

function makeTitle<Str extends string>(str: Str): `<spooky>${Uppercase<Str>}</spooky>` {
    return "<spooky>" + str.toUpperCase() + "</spooky>"
}

You'd think this would be it, but str.toUpperCase actually converts the str to a string! Tricky, you'd need to think creatively here and you have three options:

  1. Use an as because you know better than the compiler:

    function makeTitle<Str extends string>(str: Str): `<spooky>${Uppercase<Str>}</spooky>` {
        const shouty = str.toUpperCase() as Uppercase<Str>
        return `<spooky>${shouty}</spooky>`
    }
    
  2. Override toUpperCase to support template literals:

    interface String {
        toUpperCase<T extends string>(this: T) : Uppercase<T>
    }
    
  3. Or create a new function which supports template literals.

This would take the "party" used on line 19 and convert it to "<spooky>PARTY</spooky>". That change would remove the compiler error on addTadaEmoji.

The second part was about re-using the type parameters inside argument for the function. The challenge started with:

function setupFooter(str: string) {
    // validate string etc
    return { 
        name: str.split(",")[0],
        date: str.split(",")[1],
        address: str.split(",")[2]
    }
}

Would lose string literals passed in as str. You knew ahead of time that there were three separate parts of information you were interested in:

function setupFooter<Name extends string, Date extends string, Address extends string>(str: string) {

These could then be used inside the replacement for string:

function setupFooter<Name extends string, Date extends string, Address extends string>(str: `${Name},${Date},${Address}`) {

Which would correctly set up these variables for re-use later:

function setupFooter<Name extends string, Date extends string, Address extends string>(str: `${Name},${Date},${Address}`) {
    // validate string etc
    return { 
        name: str.split(",")[0] as Name,
        date: str.split(",")[1] as Date,
        address: str.split(",")[2] as Address
    }
}

Successfully completing this challenge would show that name, date and address were not string but the strings passed in.

Our answer.

The Challenge

Beginner/Learner Challenge

Make some candy bowls. Then make some very specific bowls.

Intermediate/Advanced Challenge

Run a set of pumpkin competitions

How To Share Your Solution

Once you feel you have completed the challenge, you will need to select the Share button in the playground. This will automatically copy a playground URL to your clipboard.

Then either:

  • Go to Twitter, and create a tweet about the challenge, add the link to your code and mention the @TypeScript Twitter account with the hashtag #TypeOrTreat.

  • Leave us a comment with your feedback on here!

Best Resources for Additional Help

If you need additional help you can utilize the following:

Happy Typing :)


This content originally appeared on DEV Community and was authored by Orta


Print Share Comment Cite Upload Translate Updates
APA

Orta | Sciencx (2021-10-28T19:20:37+00:00) Type | Treat 2021 – Day 4. Retrieved from https://www.scien.cx/2021/10/28/type-treat-2021-day-4/

MLA
" » Type | Treat 2021 – Day 4." Orta | Sciencx - Thursday October 28, 2021, https://www.scien.cx/2021/10/28/type-treat-2021-day-4/
HARVARD
Orta | Sciencx Thursday October 28, 2021 » Type | Treat 2021 – Day 4., viewed ,<https://www.scien.cx/2021/10/28/type-treat-2021-day-4/>
VANCOUVER
Orta | Sciencx - » Type | Treat 2021 – Day 4. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/10/28/type-treat-2021-day-4/
CHICAGO
" » Type | Treat 2021 – Day 4." Orta | Sciencx - Accessed . https://www.scien.cx/2021/10/28/type-treat-2021-day-4/
IEEE
" » Type | Treat 2021 – Day 4." Orta | Sciencx [Online]. Available: https://www.scien.cx/2021/10/28/type-treat-2021-day-4/. [Accessed: ]
rf:citation
» Type | Treat 2021 – Day 4 | Orta | Sciencx | https://www.scien.cx/2021/10/28/type-treat-2021-day-4/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.