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')
以上是最终代码。第一次写文章,如果有用词不当或理解错误的地方请大佬们指正。
使用到的新版本特性
- Template Literal Types
- Recursive Conditional Types
知乎上有对第一个新特性的评价 如何评价TypeScript新特性
在我的实现代码中,类型工具函数 Take, Drop 就应用到了这个新特性
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'> // ''
对于第二个新特性,官网介绍如下:
In TypeScript 4.1, conditional types can now immediately reference themselves within their branches, making it easier to write recursive type aliases.
在条件类型中,我们可以在它的分支上引用自身。 在 SafaPrintf 函数的类型实现中,最重要的 format 类型函数就使用到该特性
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>>> )
简单解释,通过判断是否为空字符串或者是长度为1的字符串来结束类型的递归构造,若长度大于1,则可以进行判断前两个字符是否为目标字符串
type StringToType<T extends string> = T extends '%s' ? string : T extends '%d' ? number : never;
若是 StringToType 返回为 never 则去除第一个字符后进行递归 format<drop<S>> ,若是不为 never 则说明输入的字符串中包含需要替换的目标字符串,则需要转换成函数类型,
ToFunction<StringToType<take2<S>>, Format<drop2<S>>>
函数的返回类型则是取出目标字符后的进行递归的 format 类型。
然后我们的 SafePrintf 的返回值的类型就完成了(因为T为泛型,所以我们在实现safePrint函数时根本不知道Format的类型,但是我们可以用any规避类型检查,只有我们使用safePrint函数时才知道Format的类型)
function safePrint<T extends string>(input: T): format<T> {
return print(input);
}
剩下的则是Print函数的JavaScript的实现
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);
}
这里我们safePrint的类型和具体实现是分开的,并且因为typescript有递归深度的检查所以format能接受的字符长度有限。
最终结果
最后附上 idris 的 safePrintf 函数实现
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) ""