TypeScript types to solve the worm-eaten arithmetic
Simple problem of finding 1~3 different values satisfying T1 + T2 = T3
Numbers are expressed in binary notation and addition is constructed using 1-bit logic operations.
Conditional expressions are in the form of any or never return
The and operation of a condition corresponds to an intersection of types
The test code assigns “OK” to the result type, and if the result is never, a type error occurs.
The problem I wanted to solve:
Since division can be eliminated by expression transformation, it should be possible to implement a larger number type and multiplication…
ts
type BIN = 0 | 1 ;
type XOR < A extends BIN , B extends BIN > = (
A extends 0 ? B : ( B extends 0 ? A : 0 )
)
type AND < A extends BIN , B extends BIN > = (
A extends 0 ? 0 : B
)
type B2 = [ BIN , BIN ];
type N1 = [ 0 , 1 ];
type N2 = [ 1 , 0 ];
type N3 = [ 1 , 1 ];
type ADD2DIGIT < A extends B2 , B extends B2 > = (
[
XOR < XOR < AND < A [ 1 ], B [ 1 ]>, A [ 0 ]>, B [ 0 ]>,
XOR < A [ 1 ], B [ 1 ]>
]
)
type EQUAL < A extends B2 , B extends B2 > = (
A extends B ? any : never
);
let a1 : EQUAL < ADD2DIGIT < N1 , N2 >, N3 > = "OK" ;
// let a2: EQUAL<ADD2DIGIT<N1, N1>, N3> = "OK"; // NG
// CONSTRAINT: T1 + T2 = T3
type CONSTRAINT <
T1 extends B2 ,
T2 extends B2 ,
T3 extends B2
> = (
EQUAL < ADD2DIGIT < T1 , T2 >, T3 >
);
let b1 : CONSTRAINT < N1 , N1 , N2 > = "OK" ;
// let b2: CONSTRAINT<N1, N1, N3> = "OK"; // NG
let b3 : CONSTRAINT < N2 , N1 , N3 > = "OK" ;
type IS_PERMUTATION <
T1 extends B2 ,
T2 extends B2 ,
T3 extends B2
> =
T2 extends T1 ? never : ( T3 extends ( T1 | T2 ) ? never : any );
let c1 : IS_PERMUTATION < N1 , N2 , N3 > = "OK" ;
// let c2: IS_PERMUTATION<N2, N3, N2> = "OK"; // NG
let c3 : IS_PERMUTATION < N2 , N1 , N3 > = "OK" ;
type IS_SOLUTION < T1 extends B2 , T2 extends B2 , T3 extends B2 > = (
IS_PERMUTATION < T1 , T2 , T3 > & CONSTRAINT < T1 , T2 , T3 >
);
let d1 : IS_SOLUTION < N1 , N2 , N3 > = "OK" ;
let d2 : IS_SOLUTION < N2 , N1 , N3 > = "OK" ;
// let d3: IS_SOLUTION<N1, N1, N2> = "OK"; // NG
// let d4: IS_SOLUTION<N2, N1, N2> = "OK"; // NG
Made all adders and linked lists.
but type aliases cannot be called recursively, so it is not possible to take N type arguments and call them with N-1 type arguments.
So I’m not so happy to have to write like ADDER3 calls ADDER2.
ts
type FULL_ADDER < A extends BIN , B extends BIN , CARRY extends BIN > = (
[
XOR < AND < A , B >, AND < XOR < A , B >, CARRY >>,
XOR < XOR < A , B >, CARRY >
]
);
type BODY < BIN2 extends [ BIN , BIN ]> = BIN2 [ 1 ];
type CARRY < BIN2 extends [ BIN , BIN ]> = BIN2 [ 0 ];
type LIST = BIN | [ BIN , LIST ]
type CAR < X extends LIST > = (
X extends [ BIN , LIST ] ? X [ 0 ] : X
)
type CDR < X extends LIST > = (
X extends [ BIN , LIST ] ? X [ 1 ] : never
)
type CHAIN1 <
P extends LIST , A1 extends BIN , B1 extends BIN ,
FA1 = FULL_ADDER < A1 , B1 , CAR < P >>
> = (
FA1 extends [ BIN , BIN ] ?
[
CARRY < FA1 >,
[
BODY < FA1 >,
CDR < P >
]
] : never
)
type ADDER2 <
A1 extends BIN , A2 extends BIN ,
B1 extends BIN , B2 extends BIN ,
P = FULL_ADDER < A2 , B2 , 0 >
> = (
P extends LIST ?
CHAIN1 < P , A1 , B1 >
: never
)
type ADDER3 <
A1 extends BIN , A2 extends BIN , A3 extends BIN ,
B1 extends BIN , B2 extends BIN , B3 extends BIN ,
P = ADDER2 < A2 , A3 , B2 , B3 >
> = (
P extends LIST ?
CHAIN1 < P , A1 , B1 >
: never
)
{
const a1 : ADDER3 < 1 , 1 , 1 , 1 , 1 , 1 > = [ 1 , [ 1 , [ 1 , 0 ]]]
const a2 : ADDER3 < 1 , 1 , 0 , 1 , 1 , 1 > = [ 1 , [ 1 , [ 0 , 1 ]]]
const a3 : ADDER3 < 1 , 1 , 0 , 0 , 1 , 1 > = [ 1 , [ 0 , [ 0 , 1 ]]]
}
This page is auto-translated from /nishio/TypeScriptの型で虫食い算を解く using DeepL. If you looks something interesting but the auto-translated English is not good enough to understand it, feel free to let me know at @nishio_en . I’m very happy to spread my thought to non-Japanese readers.