What are template strings really for?
Tags: javascript
Published: 2020-09-09 20:40:46

Since template strings (aka [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)) were released I was feeling that they were kind of unappreciated. 

No, of course everyone loved an ability to write like `hello${world}`, and tricks like this
```javascript
escape`<html/>`
```
worked great, but for years I was sure they can do more.

I love one not-so-well-known NoSQL graph database - [Neo4j](https://neo4j.com/), and I've been building projects with it occasionally.

It was nice, but query syntax was not so great, so I had to write like this:
```javascript
s.run('MERGE (alice:Person {name : $nameParam, age : $ageParam})', {
    nameParam: 'Alice',
    ageParam: 21
})
```
and I was literally struggling to think out the name for each variable. 

For more context: this is complex query database for research with ton of capabilities and no schema, you cannot simply create an ORM for it, so I had to write raw queries. Simple example of todo list query: _"do this task has dependent tasks of infinite deep that are blocked not by other dependencies of this task?"_.

So, turned out template strings can actually solve this problem and make this simple as hell. I've created a lib for this - [cypher-talker](https://www.npmjs.com/package/cypher-talker) and now I'm writing like this, feeling myself really happy for it:
```javascript
s.run(...t`MERGE (alice:Person {name : ${'Alice'}, age : ${21})`)
```

I'm planning to simplify it more and write a monkey-patch to write like this:
```javascript
s.run`MERGE (alice:Person {name : ${'Alice'}, age : ${21})`
```
but it requires some other driver extensions - like transactions Realms wrapper, but I'll write on it when I'm done with it.

## So, what is the trick?

Template strings are expected to be a **pure functions**. This is important: you are generally not intended to alter something in it. You can, but generally even ESLint will stop you - [no-unused-expressions](https://eslint.org/docs/rules/no-unused-expressions) rule prevents you from doing it by default.

Template literal (yes, that's how function to use with template strings called) should have a following signature:

```typescript
(literals: TemplateStringsArray, ...placeholders: string[]): any
```

What is cool: typescript fully understands signature of template function, so it will detect an error here:
```typescript
const t = (literals: TemplateStringsArray, ...placeholders: string[]) => null

t`hello${'world'}${2}`

// and even here!

const t = (literals: TemplateStringsArray, ...placeholders: [string, number, ...string[]]) => null

t`hello${'world'}${true}`
```

With typescript 4 and its advanced tuples it now works amazing!

If you're curious, what `TemplateStringsArray` is - it is simply `ReadonlyArray<string>`, nothing special.

Note that literals size is always 1 time bigger than placeholders length. It will always has a string - even empty, so its reduction may be a bit complicated.

## Second magic is that it can return anything

For my lib I needed to produce something spreadable - I mean, iterable. You can return anything: object, array, WeakRef or function. It will simply work.

I know, this looks obvious, but when you really understand what that does mean - you will see a world of possibilities.

### Imagine NestJS, but with template string decorators

```typescript
@Get`docs`
@Redirect`https://docs.nestjs.com`(302)
getDocs(@Query`version` version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}
```

I think, it looks amazing. Simply remove brackets - and now it looks really declarative. It starts to look not like bunch of function calls all around, but like a some kind of DSL, really.

If you forget how it looks like right now:
```typescript
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}
```

### Or imagine tests, but with decorator fns

```typescript
it`tests error with async/await and rejects`(async () => {
  expect.assertions(1);
  await expect(user.getUserName(3)).rejects.toEqual({
    error: 'User with 3 not found.',
  });
});
```

Looks simple, but what if we will drop some parametrized testing in it?
```typescript
[2, 3, 5, 7].forEach((value) => {
  it(`should return true for prime number ${value}`, 
() => {
    expect(isPrime(value)).toEqual(true);
  });
});

//vs

it(`should return true for prime number ${[2, 3, 5, 7]}`, 
(value: number) => {
  expect(isPrime(value)).toEqual(true);
});
```

_note: yes, I know that DoneCallback usually should be there, but I'm speaking on general concept, not specific framework_

If you think it is not possible with types: it works on nightly TS 4.1. There is issues right now with recursive conditional types in latest TS, but they are fixing it. [TS playground](https://www.typescriptlang.org/play?ts=4.1.0-insiders.20200909#code/C4TwDgpgBAxghgJwDwBUB8UC8UVQgD2AgDsATAZygG0BLYgMwgSgCUAaKAOm7mJCoC6AqAH5WUAFxRiEAG5MAUKEixSydFhx5CJCtV4gO3TnUbMWwsS0nUBCpeGhgaMANYpHqDNlwEiZSgBBBAQ4ECRTJlYMKxsZeQR7ZScXVwAxGgRyYFRtfz0qYwBXYldiAHsAd2JBQQFvLT9dSkFRWxsqZzcPSCR4dTQ0I24u9MzsvrUvNDsFGHLibKgaYE1cpoDqYtKK6tqhNAAKABsVpjhj8ikUCABbMGO4IgBlYAQ6AHNyYNDDLhHHjAIAALcrHUhMK44ACUWAwAG8FFBkVAEBBgEUEMQoId6MQpIdjIgvlJRhksjl0LDMBhZOUaKRqQj7CioABfBQchQrAAGwIgx2O5QAJPCqABySrlBDg8UCNlQUVUN5FCAcACs8p5h0Osik2XexA+HA+UmIRVuACMogAfKCW8pgiC8JlQRGc6FAA)

```typescript
// a bit of functional magic
type car<T> = T extends [infer R, ...any[]] ? R : never
type cdr<T> = T extends [any, ...infer R] ? R : []

type pickType<T> = T extends Array<infer R> ? R : never

type pickFirst<T extends [...unknown[][]]> = T extends [] 
    ? [] 
    : [pickType<car<T>>, ...pickFirst<cdr<T>>]

const it = <T extends [...unknown[][]]>(
    literals: TemplateStringsArray, ...placeholders: T
) => {
    return (fn: (...args: pickFirst<T>) => void) => {

    }
}

it`hello${['world']} ${[true, 5]}`(
(v: string, g: number | boolean) => {
 // test it!
})
```

### Conclusion

I really think that template strings are damn unappreciated. They can bring you DSL you want - and keep the glory of types.

Try to love them more!