#I get really lost when I'm writing conditional types and the branches are a WIP. Suggestions?

65 messages · Page 1 of 1 (latest)

spiral skyBOT
#
bawdyinkslinger#0

Preview:ts // This works like the linux `tr` command export const tr = <TRANSLATIONS extends string[]>( input: string, ...translations: TRANSLATIONS extends [infer FIRST, infer SECOND, ...infer REST] ? FIRST extends undefined ? SECOND extends undefined : TRANSLATIONS ...

rustic field
#

!ts

spiral skyBOT
#
// This works like the linux `tr` command
export const tr = <TRANSLATIONS extends string[]>(
  input: string,
  ...translations: TRANSLATIONS extends [infer FIRST, infer SECOND, ...infer REST] 
  ? FIRST extends undefined 
    ? SECOND extends undefined 
    : TRANSLATIONS
//  ^
// '?' expected.
        ? 
//      ^
// ':' expected.
): string => {
//
// ':' expected.
  return '';
};

rustic field
#

This doesn't compile at all. I'm in the process of writing it out, but I get lost easily:

#

I don't know what it means to put a ? or a : after : TRANSLATIONS, which choices are syntactically valid, or how to format the code in its current state to help myself answer these questions

#

and prettier won't help because it doesn't compile

#

I'm not asking how to fix this specific function, I'm asking how to make it easier on me for every conditional type I write going forward

opal sun
#

I guess you could make a snippet that fills in the whole ternary and then fill in the branches later, like:

T extends CONDITION ? THEN : ELSE
opal sun
#

VSCode snippets are handy because it'll fill the text and you can setup tab stops to tab through the parts to fill in.

#

And as long as it's syntactically valid, prettier can format it. (Doesn't matter if THEN, and ELSE aren't actual types)

rustic field
#

but THEN and ELSE don't exist. Doesn't that make it syntatically invalid?

#

or is that semantic?

#

(I thought that was syntax)

opal sun
#

Yeah, that's semantic - the syntax is just the structure of the code.

rustic field
#

That's kind of beside the point though, because prettier formats conditional types in a very unhelpful way

#

But still, your suggestion is helpful

opal sun
rustic field
#

oh wow, okay. I didn't know they ever did things like this

opal sun
#

It's very rare - they're generally against options.

#

(The plan is probably to run it as experimental for a while and if there isn't overwhelming opposition, I suspect this will just become how prettier does ternaries in the future)

rustic field
#

I'm inferring from your suggestions that you don't run into this problem?

opal sun
#

Yes and no? I can't say I've never gotten turned around writing a ternary and misplaced a ? and : and had to sort it out; but it's not a particularly common thing.

#

It's never been enough that it hits the level of being a "problem" that I need to solve.

rustic field
#

So how do you keep your place when they are nested ternaries?

rustic field
opal sun
#

At the type level, part of it is just... I don't write conditional types hardly ever.

rustic field
#

oh, hah

#

What am I doing wrong... I write one like once a month

#

If you can tell what I'm trying to do with this function, would you avoid conditional types for it?

opal sun
#

I can't fully tell, but... probably not?

#

Part of it is just I try to keep types simple in day-to-day code, and if I'm getting into unsafe function territory, I generally find overloads easier to work with where possible.

rustic field
#

one sec, let me show you some test cases

#
import { tr } from './tr';

fdescribe(`tr`, () => {
  it('has no effect when no translation is required', async () => {
    expect(tr(`foobar`, `z`, `z`)).toEqual(`foobar`);
    expect(tr(`foobar`, `a`, `a`, `z`, `z`)).toEqual(`foobar`);
  });
  
  it('can translate one character', async () => {
    expect(tr(`foobar`, `a`, `i`)).toEqual(`foobir`);
    expect(tr(`foobar`, `r`, `a`, `f`, `z`)).toEqual(`zoobaa`);
  });
  
  
  it('can translate multiple characters', async () => {
    expect(tr(`foobar`, `o`, `u`)).toEqual(`fuubar`);
  });

  it('never translates translated characters', async () => {
    expect(tr(`foobar`, `o`, `a`, `a`, `z`)).toEqual('faabzr');
  });
});
opal sun
#

So the requirement is basically "one string, plus pairs of strings that follow it"?

rustic field
#

But I can do anything I want. I'm writing this all from scratch

#

If you would approach this differently, I'd like to know how you think about the problem

#

as I'm 99% self-taught when it comes to typescript

opal sun
#

Yeah, I think the most likely thing I'd do is tweak the signature:

function tr(input: string, ...pairs: Array<[from: string, to: string]>) {}

tr("foobar", ["r", "a"], ["f", "z"] )
rustic field
#

That's certainly easier to type (in the ts type sense)

opal sun
#

I think there also might be a trick for doing the pairs thing directly.

rustic field
#

why Array<[from: string, to: string]> instead of [string, string][]?

#

do those mean different things (besides naming the elements)

opal sun
#

No, the labels just help clarify what the individual parts of the tuple are for.

#

(And I always use Array for anything other than a single identifier, just a style/readability rule)

rustic field
#

Interesting, I was use T[]

opal sun
rustic field
#

I don't think it is because I've asked how to do something like that in the past and I think everybody gave me some deeply nested conditional types as a solution

#

And I spent a lot of time trying to find a way to do this too

#

If it can be done, I wish it were documented.it seems like variadic tuples practically scream this can be done, but afaict, they can't be used for this

opal sun
#

Yeah, maybe not. I kinda thought I've seen someone make a type that was like [A, (B, C)...] (e.g. [A, B, C, B, C, B, C], but again, not having a ton of luck.

rustic field
opal sun
#

Yeah, I kinda think the grouping helps make the function easier to understand anyway.

rustic field
#

Same type safety, a little inconvenient to use, but 100x easier for me to implement as a type signature

rustic field
#

@opal sun Thanks again for the help!

#

!resolve

opal sun
#

One last thing, like I said, when I do reach for complex typings, I tend to like overloads. Not always elegant, but pretty easy to reason about. Like if you really wanted this to be flat, could theoretically do:

spiral skyBOT
#
function translate(x: string, ...replacements: [string, string]): string
function translate(x: string, ...replacements: [string, string, string, string]): string
function translate(x: string, ...replacements: [string, string, string, string, string, string]): string
function translate(x: string, ...replacements: [string, string, string, string, string, string, string, string]): string
function translate(x: string, ...replacements: string[]) {
  return ""
}

translate("foo", "bar", "baz", "other")
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// No overload expects 4 arguments, but overloads do exist that expect either 3 or 5 arguments.
rustic field
#

type type

opal sun
#

Obviously the annoying thing is the cap on the replacements, but in practice "pick a cap, add overloads if you go over it" is probably not bad.