type Take2<T extends string> = T extends `${infer h1}${infer h2}${infer tail}` ? `${h1}${h2}` : never;
type Drop<T extends string> = T extends `${infer head}${infer tail}` ? tail : never;
type Drop2<T extends string> = T extends `${infer h1}${infer h2}${infer tail}` ? tail : never;
type ToFunction<T, R> = (t: T) = > R;
type StringToType<T extends string> = T extends '%s' ? string : T extends '%d' ? number : never;
type FormatEnd<T extends string> =
T extends ' '
? true
: (Drop<T> extends ' ' ? true : false)
type Format<S extends string> =
FormatEnd<S> extends true
? string
: ( StringToType<Take2<S>> extends never ? Format<Drop<S>> : ToFunction<StringToType<Take2<S>>, Format<Drop2<S>>> )
function print(s: string, prev: string = ' ') :any {
if (s.length <= 1) return prev + s;
const [h1, h2, ...tail] = s.split(' ');
if (h1 + h2 === '%s' || h1 + h2 === '%d') {
return (s: string | number) = > print(tail.join(' '), prev + s);
}
return print([h2, ...tail].join(' '), prev + h1);
}
function safePrint<T extends string> (input: T) :Format<T> {
return print(input);
}
safePrint('11%d222%s888%d')
Copy the code
This is the final code. The first time to write an article, if there are improper words or misunderstanding of the place to ask the leaders correct.
New version features used
- Template Literal Types
- Recursive Conditional Types
What do you think of the new TypeScript feature
In my implementation code, the type utility functions Take, Drop apply this new feature
type Take2<T extends string> = T extends `${infer h1}${infer h2}${infer tail}` ? `${h1}${h2}` : never;
type Drop<T extends string> = T extends `${infer head}${infer tail}` ? tail : never;
type Drop2<T extends string> = T extends `${infer h1}${infer h2}${infer tail}` ? tail : never;
type test1 = Take2<' '> // never
type test2 = Take2<'1'> // never
type test3 = Take2<'12'> / / '12'
type test4 = Drop<' '> // never
type test5 = Drop<'1'> / /"
type test6 = Drop2<'12'> / /"
Copy the code
The second new feature is described on the official website as follows:
In TypeScript 4.1, conditional types can now immediately reference themselves within their branches, making it easier to write recursive type aliases.
In a condition type, we can refer to ourselves on its branches. In the type implementation of the SafaPrintf function, the most important format function uses this feature
type Format<S extends string> =
FormatEnd<S> extends true
? string
: ( StringToType<Take2<S>> extends never ? Format<Drop<S>> : ToFunction<StringToType<Take2<S>>, Format<Drop2<S>>> )
Copy the code
To simplify, the recursive construction of a type is terminated by determining whether it is an empty string or a string of length 1. If the length is greater than 1, the first two characters are the target string
type StringToType<T extends string> = T extends '%s' ? string : T extends '%d' ? number : never;
Copy the code
If StringToType returns never, the format
> recursively removes the first character. If it is not never, the input string contains the target string to be replaced and needs to be converted to a function type.
ToFunction<StringToType<take2<S>>, Format<drop2<S>>>
Copy the code
The return type of the function is the recursive format type after the target character is retrieved.
(Since T is generic, we don’t know the type of Format at all when we implement the safePrint function, but we can use any to get around type checking. We only know the type of Format when we use the safePrint function.)
function safePrint<T extends string> (input: T) :format<T> {
return print(input);
}
Copy the code
What is left is the JavaScript implementation of the Print function
function print(s: string, prev: string = ' ') :any {
if (s.length <= 1) return prev + s;
const [h1, h2, ...tail] = s.split(' ');
if (h1 + h2 === '%s' || h1 + h2 === '%d') {
return (s: string | number) = > print(tail.join(' '), prev + s);
}
return print([h2, ...tail].join(' '), prev + h1);
}
Copy the code
Here we keep safePrint’s type and implementation separate, and because typescript has recursion-deep checking, format accepts a limited number of characters.
The final result
Finally, the safePrintf function of IDRIS is attached
You can use idRIS to implement the printf function
module Printf
%default total
data Format = FInt Format
| FString Format
| FOther Char Format
| Fend
format : List Char -> Format
format ('%'::'d': :cs) = FInt (format cs)
format ('%'::'s': :cs) = FString (format cs)
format (c:cs) = FOther c (format cs)
format [] = FEnd
interpFormat : Format -> Type
interpFormat (FInt f) = Int -> interpFormat f
interpFormat (FString f) = String -> interpFormat f
interpFormat (FOther _ f) = interpFormat f
interpFormat FEnd = String
formatString : String -> Format
formatString s = format (unpack s)
toFunction : (fmt : Format) -> String -> interpFormat fmt
toFunction (FInt f) a => \i -> toFunction f (a ++ show i)
toFunction (FString f) a => \s -> toFunction f (a ++ s)
toFunction (FOther c f) a => toFunction f (a ++ singleton c)
toFunction FEnd a = a
printf : (s : String) -> interpFormat (formatString s)
printf s = toFunction (formatString s) ""
Copy the code