Last active
March 3, 2020 20:19
-
-
Save timjb/8b0f1b734c3cbb1645d0dd8f63c164fb to your computer and use it in GitHub Desktop.
Vortrag über Programmierung auf Typebene in TypeScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace BigNats { | |
type RepeatHelper<N extends number, T extends any, CurrList extends T[], CurrSizes extends number[]> = | |
CurrSizes extends [1] | |
? (CurrList["length"] extends N ? ["found", CurrList] : ["not-found", Cons<T,CurrList>]) | |
: { | |
"true": RepeatHelper<N, T, CurrList, Tail<CurrSizes>> extends ["not-found", infer NewCurrList] | |
? RepeatHelper<N, T, Cast<NewCurrList, T[]>, Tail<CurrSizes>> | |
: RepeatHelper<N, T, CurrList, Tail<CurrSizes>> | |
}[CurrSizes extends [-1] ? "not-going-to-happen" : "true"]; | |
type InitialSizes = [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1]; | |
type Repeat<N extends number, T extends any> = RepeatHelper<N, T, [], InitialSizes>[1]; | |
type EightHundredAndSixtySeven = Repeat<867, any>; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Integers { | |
type Length<L extends any[]> = L['length']; | |
type ListNat = undefined[]; | |
type ListSucc<LN extends ListNat> = Cons<undefined, LN>; | |
type ListPred<LN extends ListNat> = Tail<LN>; | |
type ToListNat<N extends number> = ToListNatRec<N, []>; | |
// type ToListNatRec<N extends number, LN extends any[]> = | |
// Length<LN> extends N ? LN : ToListNatRec<N, ListSucc<LN>>; | |
type ToListNatRec<N extends number, LN extends ListNat> = | |
{ | |
"true": LN, | |
"false": ToListNatRec<N, ListSucc<LN>>, | |
}[Length<LN> extends N ? "true" : "false"]; | |
// type ListNatAdd<LN extends ListNat, LM extends ListNat> = | |
// ((...args: LN) => void) extends (head: undefined, ...tail: infer LNPrime) => void | |
// ? ListSucc<ListNatAdd<LNPrime, LM>> | |
// : LM; | |
// type ListNatAdd<LN extends ListNat, LM extends ListNat> = | |
// { | |
// "true": LM, | |
// "false": ListSucc<ListNatAdd<ListPred<LN>, LM>>, | |
// }[Length<LN> extends 0 ? "true" : "false"]; | |
type TypeLevelError<DefinitionName extends string, ArgumentName extends string, T extends any, ExpectedSupertype extends any> = | |
T extends ["Error in application of", string, ": argument", string, "does not extend", any, ". Actual type is", any] | |
? T | |
: ["Error in application of", DefinitionName, ": argument", ArgumentName, "does not extend", ExpectedSupertype, ". Actual type is", T]; | |
type AddNatToListNat<N extends number, LM extends any> = | |
LM extends ListNat | |
? AddNatToListNatRec<N, LM, []> | |
: TypeLevelError<"AddNatToListNat", "LM", LM, ListNat>; | |
type AddNatToListNatRec<N extends number, LM extends ListNat, LN extends ListNat> = | |
{ | |
"true": LM, | |
"false": AddNatToListNatRec<N, ListSucc<LM>, ListSucc<LN>>, | |
}[Length<LN> extends N ? "true" : "false"]; | |
type ToNumber<LN extends any> = | |
LN extends ListNat ? Length<LN> : | |
LN extends Minus<ListNat> ? Minus<Length<Value<LN>>> : | |
TypeLevelError<"ToNumber", "LN", LN, ListNat | Minus<ListNat>>; | |
type AddNat<N extends number, M extends number> = ToNumber<AddNatToListNat<N, ToListNat<M>>>; | |
// type Minus<N> = ["-", N]; | |
// type Value<T extends Minus<any>> = T[1]; | |
interface Minus<N> { | |
value: N; | |
} | |
type Value<T extends Minus<any>> = T["value"]; | |
type Integer = number | Minus<number>; | |
type ListNatMinusListNat<LN extends any, LM extends any> = | |
LN extends ListNat | |
? (LM extends ListNat | |
? ListNatMinusListNatHelper<LN, LM> | |
: TypeLevelError<"ListNatMinusListNat", "LM", LM, ListNat>) | |
: TypeLevelError<"ListNatMinusListNat", "LN", LN, ListNat>; | |
type ListNatMinusListNatHelper<LN extends ListNat, LM extends ListNat> = | |
{ | |
"LM-is-zero": LN, | |
"LN-is-zero": Minus<LM>, | |
"both-are-succ": ListNatMinusListNat<ListPred<LN>, ListPred<LM>>, | |
}[Length<LM> extends 0 ? "LM-is-zero" : Length<LN> extends 0 ? "LN-is-zero" : "both-are-succ"]; | |
type SubtractNat<N extends number, M extends number> = ToNumber<ListNatMinusListNat<ToListNat<N>, ToListNat<M>>> | |
type Add<N extends Integer, M extends Integer> = | |
N extends number ? | |
(M extends number ? AddNat<N, M> : | |
M extends Minus<number> ? SubtractNat<N, Value<M>> : | |
TypeLevelError<"Add", "M", M, Integer>) : | |
N extends Minus<number> ? | |
(M extends number ? SubtractNat<M, Value<N>> : | |
M extends Minus<number> ? Minus<AddNat<Value<N>, Value<M>>> : | |
TypeLevelError<"Add", "M", M, Integer>) : | |
TypeLevelError<"Add", "N", N, Integer>; | |
// Examples: | |
type ThreePlusMinusSeven = Add<3, Minus<7>>; | |
type TwoPlusThree = Add<2, 3>; | |
type MinusFourPlus3 = Add<Minus<4>, 3> | |
type MinusSevenPlusMinusEight = Add<Minus<7>, Minus<8>>; | |
interface Quantity { | |
length: Integer; | |
time: Integer; | |
mass: Integer; | |
} | |
type Velocity = { | |
length: 1; | |
time: Minus<1>; | |
mass: 0; | |
}; | |
type Newton = { | |
length: 1; | |
time: Minus<2>; | |
mass: 1; | |
} | |
type Frequency = { | |
length: 0; | |
time: Minus<1>; | |
mass: 0; | |
} | |
// type MultQuantities<Q extends Quantity, R extends Quantity> = { | |
// length: Add<Q["length"], R["length"]>; | |
// time: Add<Q["time"], R["time"]>; | |
// mass: Add<Q["mass"], R["mass"]>; | |
// } | |
type MultQuantities<Q extends Quantity, R extends Quantity> = { | |
[Dimension in keyof Quantity]: Add<Q[Dimension], R[Dimension]>; | |
} | |
type Watt = MultQuantities<Newton,Frequency>; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- | De Bruijn Index | |
type DBI = Int | |
data LC | |
= Var DBI | |
| App LC LC | |
| Abs LC | |
| Succ | |
| Nat Int | |
deriving (Show) | |
-- TODO: hier ein paar Beispiele | |
sub :: LC -> DBI -> LC -> LC | |
sub term v substitute = | |
case term of | |
Var dbi -> if dbi == v then substitute else term | |
App fun arg -> App (sub fun v substitute) (sub arg v substitute) | |
Abs body -> Abs (sub body (succ v) substitute) -- nur korrekt, falls substitute keine freien Variablen enthält | |
_ -> term | |
eval :: LC -> LC | |
eval term = | |
case term of | |
App fun arg -> | |
case eval fun of | |
Abs body -> eval (sub body 0 arg) | |
Succ -> | |
case eval arg of | |
Nat i -> Nat (succ i) | |
_ -> error "keine Zahl!" | |
_ -> error ":-(" | |
_ -> term | |
-- Church Encoding | |
-- https://en.wikipedia.org/wiki/Church_encoding | |
infixl 6 € | |
(€) :: LC -> LC -> LC | |
(€) = App | |
zero :: LC | |
zero = Abs (Abs (Var 0)) -- \f. \x. x | |
one :: LC | |
one = Abs (Abs (Var 1 € Var 0)) -- \f. \x. f x | |
plus :: LC -- \m. \n. \f. \x. m f (n f x) | |
plus = Abs (Abs (Abs (Abs (Var 3 € Var 1 € (Var 2 € Var 1 € Var 0))))) | |
two :: LC | |
two = plus € one € one | |
-- TODO: mult? | |
mult :: LC -- \m. \n. \f. \x. m (n f) x = | |
-- \m. \n. \f. m (n f) | |
mult = Abs (Abs (Abs (Var 2 € (Var 1 € Var 0)))) | |
potenz :: LC -- \m. \n. \f. \x. n m f x = | |
-- \m. \n. n m | |
potenz = Abs (Abs (Var 0 € Var 1)) | |
toNat :: LC | |
toNat = Abs (Var 0 € Succ € Nat 0) | |
nonTerminating :: LC | |
nonTerminating = applyToSelf € applyToSelf | |
where applyToSelf = Abs (Var 0 € Var 0) | |
-- TODO: Y Combinator? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace LambdaCalculus { | |
// Variables | |
interface Var<DBI extends any[]> { | |
var: DBI; // De Bruijn index | |
} | |
// Application | |
interface App<Fun extends LC, Arg extends LC> { | |
fun: Fun; | |
arg: Arg; | |
} | |
// Abstraction (Lambdas!) | |
interface Abs<Body extends LC> { | |
body: Body; | |
} | |
interface SuccFun { | |
succFun: "succFun"; | |
} | |
interface Nat<Val extends any[]> { | |
val: Val | |
} | |
type LC = Var<any[]> | App<LC, LC> | Abs<LC> | SuccFun | Nat<any[]> | |
type Sub<Term extends LC, V extends any[], Substitute extends LC> = | |
Term extends Var<infer DBI> ? | |
(DBI extends V ? Substitute : Term) : | |
Term extends App<infer Function, infer Arg> ? | |
App<Sub<Function,V,Substitute>,Sub<Arg,V,Substitute>> : | |
Term extends Abs<infer Body> ? | |
Abs<Sub<Body,Succ<V>,Substitute>> : // nur korrekt, falls Substitute keine freien Variablen enthält | |
Term; | |
// Erster Versuch: | |
// type Eval<Term extends LC> = | |
// Term extends App<infer Function, infer Arg> | |
// ? (Eval<Function> extends Abs<infer Body> ? Sub<Body, [], Arg> : ":-(") | |
// : Term; | |
// Zweiter Versuch: | |
type Eval<Term extends LC> = | |
Term extends App<infer Fun, infer Arg> | |
? ({ | |
"Rekursion": | |
Eval<Fun> extends infer E ? | |
E extends Abs<infer Body> ? | |
Eval<Sub<Body, [], Arg>> : | |
E extends SuccFun ? | |
(Eval<Arg> extends Nat<infer Val> ? Nat<Succ<Val>> : "keine Zahl!") : | |
":-(" : | |
"passiert nicht"; | |
}[Fun extends Var<[]> ? "Rekursion" : "Rekursion"]) | |
: Term; | |
type V<DBI extends number> = ToAnyList<DBI> extends infer N ? Var<Cast<N, any[]>> : "too bad"; | |
type Zero = Abs<Abs<V<0>>> | |
type One = Abs<Abs<App<V<1>,V<0>>>> | |
type PlusLC = | |
Abs<Abs<Abs<Abs< | |
App< | |
App<V<3>, V<1>>, | |
App< | |
App<V<2>,V<1>>, | |
V<0> | |
> | |
> | |
>>>>; | |
type Two = App<App<PlusLC,One>,One>; | |
type ToNat = Abs<App<App<V<0>,SuccFun>,Nat<[]>>>; | |
type Three = Eval<App<ToNat,App<App<PlusLC,One>,Two>>>; | |
type ApplyToSelf = Abs<App<V<0>,V<0>>>; | |
type NonTerminating = App<ApplyToSelf,ApplyToSelf>; | |
// type NonTerminating1 = Eval<NonTerminating>; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Programmieren auf Typebene in TypeScript | |
// ======================================== | |
// (getestet mit TypeScript 3.7.3) | |
// (online als Gist: https://gist.github.com/timjb/8b0f1b734c3cbb1645d0dd8f63c164fb) | |
// Diese Datei kann ausgeführt werden mit `ts-node livecoding.ts` | |
// Primitive Typen | |
const falsch: boolean = false; | |
const antwort: number = 42.23; | |
const beispielString: string = "foo"; | |
// Zusammengesetzte Typen | |
const gruesse = (name: string) => `Hallo ${name.toUpperCase()}!`; | |
type Greeter = (name: string) => string; | |
// const gruesse: Greeter = name => `Hallo ${name.toUpperCase()}!`; | |
const gruss = gruesse("Curry Club"); | |
// jetzt Typfehler zeigen bei zusaetzlichem Argument | |
const einObjekt = { einSchluessel: "foo" } | |
// type ObjektMitSchluessel = { einSchluessel: string } | |
// interface ObjektMitSchluessel { | |
// einSchluessel: string; | |
// } | |
// const einObjekt: ObjektMitSchluessel = { einSchluessel: "foo" } | |
// jetzt zeigen, dass TypeScript zusaetzliche Keys ablehnt | |
// Vereinigungstypen (union types) | |
type NummerOderString = number | string; | |
const eineNummer: NummerOderString = 313; | |
const einString: NummerOderString = "donald"; | |
const undefiniert = undefined; | |
const billionDollarMistake = null; | |
const parseDecimalDigit: (char: string) => number | undefined = char => { | |
if (char.length !== 1) { return undefined; } | |
const charCode = char.charCodeAt(0); | |
if (48 <= charCode && charCode <= 57) { return charCode - 48; } | |
return undefined; | |
}; | |
console.log(`parseHex('B') = ${parseDecimalDigit('B')}`); | |
console.log(`parseHex('x') = ${parseDecimalDigit('x')}`); | |
const irgendwas: any = 42; | |
// const irgendwas: any = "haskell"; | |
// const irgendwas: any = undefined; | |
// const irgendwas: any = {}; | |
const unbekannt: unknown = 42; | |
// console.log(gruesse(irgendwas)); // Laufzeitfehler! | |
// console.log(gruesse(unbekannt)); | |
// die folgenden Typen erst ohne Feld "art" programmieren | |
interface Kreis { | |
art: "kreis"; // Literaltyp | |
radius: number; | |
} | |
// const einheitsKreis: Kreis = { radius: 1 }; | |
const einheitsKreis: Kreis = { art: "kreis", radius: 1 }; | |
interface Rechteck { | |
art: "rechteck"; | |
breite: number; | |
hoehe: number; | |
} | |
// const quadrat: Rechteck = { breite: 1, hoehe: 1 } | |
const quadrat: Rechteck = { art: "rechteck", breite: 1, hoehe: 1 } | |
type Figur = Rechteck | Kreis; | |
const flaecheninhalt = (figur: Figur): number => { | |
switch (figur.art) { | |
case "rechteck": | |
return figur.breite * figur.hoehe; | |
case "kreis": | |
return figur.radius * figur.radius * Math.PI; | |
} | |
}; | |
console.log(`flaecheninhalt(einheitsKreis) = ${flaecheninhalt(einheitsKreis)}`); | |
console.log(`flaecheninhalt(quadrat) = ${flaecheninhalt(quadrat)}`); | |
// Literaltypen gibt es auch für Zahlen | |
const vier: 4 = 4; | |
// und Booleans | |
const wahr: true = true; | |
// Array-Typen | |
const fibU50: number[] = [1,1,2,3,5,8,13,21,34]; | |
// Generics | |
const init = <A>(arr: A[]): A[] => arr.slice(0, arr.length - 1); | |
console.log(`init(fibU50) = ${JSON.stringify(init(fibU50))}`); | |
// Tupel | |
type TupelTyp = [string, number, boolean]; | |
const tupel: TupelTyp = ["prim", 10, false]; | |
const tupelLänge = tupel.length; | |
// Indices | |
type KreisRadiusTyp = Kreis["radius"] | |
type FigurArten = Figur["art"]; | |
type TupelLaenge = TupelTyp["length"]; | |
// Rest-Argumente | |
const lispToMathNotation = (fnName: string, ...args: (string | number)[]) => `${fnName}(${args.join(', ')})`; | |
console.log(lispToMathNotation("pow", 2, 4)); | |
//type RepeatStringFn = (...args: [string, number]) => string; | |
const doesExtend: "foo" extends string ? true : false = true; | |
// ausprobieren mit: | |
// "foo" extends number | |
// string extends number | string | |
// { foo: number } extends {} | |
// (() => string) extends (() => number | string) | |
// ((foo: number) => void) extends ((foo: number | string) => void) | |
// ((foo: number | string) => void) extends ((foo: number) => void) | |
// (() => void) extends ((foo: number) => void) | |
// Diagramm aller TypeScript-Typen zeigen | |
// https://gist.github.com/laughinghan/31e02b3f3b79a4b1d58138beff1a2a89 | |
// infer-enz | |
type Rueckgabetyp<T> = T extends () => infer R ? R : "keine Funktion!"; | |
type R1 = Rueckgabetyp<() => string>; | |
type R2 = Rueckgabetyp<boolean>; | |
type R3 = Rueckgabetyp<(foo: string) => boolean>; | |
// richtigerer Typ: | |
//type Rueckgabetyp<T> = T extends (...args: any[]) => infer R ? R : "keine Funktion!"; | |
type Index<O,K extends string> = O extends { [k in K]: infer V } ? V : undefined; | |
type I1 = Index<{foo: "bar"}, "foo">; | |
type I2 = Index<{foo: 1, bar: 2}, string>; | |
type I3 = Index<3, string>; | |
// Tupeltypen als Ersatz für Listen auf Typebene | |
// --------------------------------------------- | |
type Head<T extends any[]> = T[0]; | |
type Head1 = Head<["foo", "bar"]>; | |
type Cons<H, T extends any[]> = | |
((head: H, ...tail: T) => void) extends (...args: infer U) => void | |
? U | |
: T; | |
type Cons1 = Cons<boolean, [number, string]>; | |
type Cons2 = Cons<boolean, string[]>; | |
type Tail<T extends any[]> = | |
((...ts: T) => void) extends (_head: any, ...tail: infer T) => void | |
? T | |
: []; | |
type Tail1 = Tail<Cons1>; | |
// Jetzt implementieren wir eine rekursive Funktion | |
// reverseAppend :: [a] -> [a] -> [a] | |
// mit der Eigenschaft | |
// reverseAppend xs ys == reverse xs ++ ys | |
// | |
// Basisfall: | |
// reverseAppend [] ys = ys | |
// | |
// Rekursiver Schritt: | |
// reverseAppend (x:xs) ys = reverseAppend xs (x:ys) | |
// Erster Versuch: | |
// type ReverseAppend<Xs extends any[], Ys extends any[]> = | |
// Xs extends [] | |
// ? Ys | |
// : ReverseAppend<Tail<Xs>, Cons<Head<Xs>, Ys>>; | |
// Richtig | |
type ReverseAppend<Xs extends any[], Ys extends any[]> = | |
{ | |
"Basisfall": Ys, | |
"Rekursiver Schritt": ReverseAppend<Tail<Xs>, Cons<Head<Xs>, Ys>>, | |
}[Xs extends [] ? "Basisfall" : "Rekursiver Schritt"]; | |
type ReverseAppend1 = ReverseAppend<[1, 2, 3, 4], [5, 6, 7, 8]>; | |
type Reverse<Xs extends any[]> = ReverseAppend<Xs, []>; | |
// nächste Definition erstmal überspringen: | |
type Cast<X, Y> = X extends Y ? X : Y; | |
type Cast1 = Cast<3, number>; | |
type Cast2 = Cast<string, number>; | |
// Implementiere (++): | |
// Erster Versuch: | |
// type Append<Xs extends any[], Ys extends any[]> = ReverseAppend<Reverse<Xs>, Ys>; | |
// Zweiter Versuch: | |
// type Append<Xs extends any[], Ys extends any[]> = | |
// Reverse<Xs> extends infer RevXs ? ReverseAppend<RevXs, Ys> : []; | |
// Wir brauchen Casting! | |
// Richtig: | |
type Append<Xs extends any[], Ys extends any[]> = | |
Reverse<Xs> extends infer RevXs ? ReverseAppend<Cast<RevXs, any[]>, Ys> : []; | |
type Append1 = Append<[1,2,3], [3,4]>; | |
// Arithmetik auf Typ-Ebene | |
// ------------------------ | |
// Idee: Repräsentiere natürliche Zahlen durch Tupeltypen, die any[] extenden: | |
// 0 = [] | |
// 1 = [any] | |
// 2 = [any,any] | |
// usw. | |
// Addition ist dann Zusammenhängen von Tupeln. | |
type Succ<Xs extends any[]> = Cons<any, Xs>; | |
type Succ1 = Succ<[any, any]>; | |
// Konvertierung einer so repräsentierten Zahl zu `number`: | |
type ToNumber<Xs extends any[]> = Xs["length"]; | |
type ToNumber1 = ToNumber<[any, any, any]>; | |
// In die andere Richtung mittels "unbeschränkter" rekursiver Suche: | |
type ToAnyList<N extends number> = Cast<ToAnyListHelper<N, []>, any[]>; | |
type ToAnyListHelper<N extends number, Current extends any[]> = | |
{ | |
"Found": Current, | |
"Keep searching": ToAnyListHelper<N, Succ<Current>>, | |
}[ToNumber<Current> extends N ? "Found" : "Keep searching"]; | |
type ToAnyList1 = ToAnyList<7>; | |
type ToAnyList2 = ToAnyList<41>; | |
// type ToAnyList3 = ToAnyList<42>; | |
// type ToAnyList4 = ToAnyList<44>; | |
// type ToAnyList5 = ToAnyList<45>; | |
// Grund: Rekursionstiefenbeschränkung von 50 demonstrieren | |
// Addition auf `number`: | |
// type Plus<M extends number, N extends number> = ToNumber<ReverseAppend<ToAnyList<M>, ToAnyList<N>>>; | |
type Plus<M extends number, N extends number> = | |
ToAnyList<M> extends infer MList | |
? ToAnyList<N> extends infer NList | |
? ReverseAppend<Cast<MList, any[]>, Cast<NList, any[]>> extends infer A | |
? ToNumber<Cast<A, any[]>> | |
: "passiert ebenfalls nicht" | |
: "passiert auch nicht" | |
: "passiert nicht"; | |
// Oder etwas kürzer: | |
// type Plus<M extends number, N extends number> = | |
// [ToAnyList<M>, ToAnyList<N>] extends [infer MList, infer NList] | |
// ? ReverseAppend<Cast<MList, any[]>, Cast<NList, any[]>> extends infer A | |
// ? ToNumber<Cast<A, any[]>> | |
// : "passiert ebenfalls nicht" | |
// : "passiert nicht"; | |
type Plus1 = Plus<7,8>; | |
// BigNats.ts: Umgeht Größenbeschränkung durch binäre Suche | |
// Integers.ts: Negative Zahlen, Subtraktion und physikalische Größen | |
// TODO: Multiplikation? | |
// TODO: Potenzieren? | |
// Im rekursiven Fall muss ein "Aufruf" desselben Typs stehen. | |
// Das ist eine kleinere Einschränkung als es erst einmal erscheint. | |
// Currying | |
// -------- | |
function simpleCurry<X, Xs extends any[], R>(fn: (firstArg: X, ...restArgs: Xs) => R): (firstArg: X) => (...restArgs: Xs) => R { | |
return firstArg => (...restArgs) => fn(firstArg, ...restArgs); | |
} | |
console.log(simpleCurry(lispToMathNotation)("K")("G", "n")); | |
type Curry<Args extends any[], R> = Args extends [] ? R : (x: Head<Args>) => Curry<Tail<Args>, R>; | |
function curry<Xs extends any[], R>(fn: (...args: Xs) => R): Curry<Xs, R> { | |
return curryHelper(fn, fn.length); | |
} | |
function curryHelper<Xs extends any[], R>(fn: (...args: Xs) => R, numArgs: Xs["length"]): Curry<Xs, R> { | |
if (hasNoArguments(fn, numArgs)) { | |
return (fn() as unknown) as Curry<Xs, R>; | |
} | |
return ((x: Head<Xs>) => curryHelper((...restArgs: Tail<Xs>) => (fn as (x: Head<Xs>, ...restArgs: Tail<Xs>) => R)(x, ...restArgs), numArgs - 1) as unknown) as Curry<Xs, R>; | |
}; | |
function hasNoArguments<Xs extends any[], R>(fn: (...args: Xs) => R, numArgs: Xs["length"]): fn is (() => R) { | |
return numArgs === 0; | |
}; | |
const det = (a: number, b: number, c: number, d: number) => a*d - b*c; | |
const det1 = curry(det)(4)(2)(2)(1); | |
console.log(`det1 = ${det1}`); | |
// Mit tonnenweise zusätzlicher Komplikationen: | |
// https://www.freecodecamp.org/news/typescript-curry-ramda-types-f747e99744ab/ | |
// Lambda-Kalkül | |
// ------------- | |
// siehe LC.hs | |
// Variables | |
interface Var<DBI extends any[]> { | |
var: DBI; // De Bruijn index | |
} | |
// Application | |
interface App<Fun extends LC, Arg extends LC> { | |
fun: Fun; | |
arg: Arg; | |
} | |
// Abstraction (Lambdas!) | |
interface Abs<Body extends LC> { | |
body: Body; | |
} | |
type LC = Var<any[]> | App<LC, LC> | Abs<LC> | |
type Sub<Term extends LC, V extends any[], Substitute extends LC> = | |
Term extends Var<infer DBI> ? | |
(DBI extends V ? Substitute : Term) : | |
Term extends App<infer Function, infer Arg> ? | |
App<Sub<Function,V,Substitute>,Sub<Arg,V,Substitute>> : | |
Term extends Abs<infer Body> ? | |
Abs<Sub<Body,Succ<V>,Substitute>> : // nur korrekt, falls Substitute keine freien Variablen enthält | |
Term; | |
// Erster Versuch: | |
// type Eval<Term extends LC> = | |
// Term extends App<infer Function, infer Arg> | |
// ? (Eval<Function> extends Abs<infer Body> ? Sub<Body, [], Arg> : ":-(") | |
// : Term; | |
// Zweiter Versuch: | |
type Eval<Term extends LC> = | |
Term extends App<infer Fun, infer Arg> | |
? ({ | |
"Rekursion": Eval<Fun> extends Abs<infer Body> ? Eval<Sub<Body, [], Arg>> : ":-(" | |
}[Fun extends Var<[]> ? "Rekursion" : "Rekursion"]) | |
: Term; | |
type V<DBI extends number> = ToAnyList<DBI> extends infer N ? Var<Cast<N, any[]>> : "too bad"; | |
type Zero = Abs<Abs<V<0>>> | |
type One = Abs<Abs<App<V<1>,V<0>>>> | |
type PlusLC = | |
Abs<Abs<Abs<Abs< | |
App< | |
App<V<3>, V<1>>, | |
App< | |
App<V<2>,V<1>>, | |
V<0> | |
> | |
> | |
>>>>; | |
type Two = Eval<App<App<PlusLC,One>,One>>; | |
// Fazit: Das Typsystem von TypeScript ist prinzipiell Turing-vollständig, wäre da nicht die Beschränkung von | |
// maximal 5.000.000 Instanziierungen / maximale Instanziierungstiefe 50 :-) | |
// Weitere Ideen | |
// ------------- | |
// TODO: Distributivität von Union-Types? | |
// TODO: Versuch, Cons direkt zu implementieren? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
{ | |
"compilerOptions": { | |
"strict": true | |
}, | |
"include": [ | |
"./**/*.ts" | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment