////////// Exercice 1 ////////// // A type for functions on numbers type numfun = (_: number) => number; // A dummy polynomial for the tests function myPolynomial(x: number): number { return 3 * x * x + 4.7; } // A first version without currying function differentiate2(h: number, f: numfun): numfun { return (x) => (f(x + h) - f(x - h)) / (2 * h); } function testDiffType(aValStr: string, aVal: any, aExpected: string) { const res = typeof aVal == aExpected ? greenString("OK") : redString("KO"); console.log(`Test type of ${aValStr} = '${aExpected}': ${res}`); } testDiffType("differentiate2(0.001, myPolynomial)", differentiate2(0.001, myPolynomial), "function"); testDiffType("differentiate2(0.001, myPolynomial)(1)", differentiate2(0.001, myPolynomial)(1), "number"); testDiffType("differentiate2(0.00001, Math.sin)", differentiate2(0.00001, Math.sin), "function"); testDiffType("differentiate2(0.00001, Math.sin)(1)", differentiate2(0.00001, Math.sin)(1), "number"); // Specialize differentiate2 on h=0.1 const specDiff2OnH: (f: numfun) => numfun = (f) => differentiate2(0.01, f); // Specialize differentiate2 on f=myPolynomial const specDiff2OnF: (h: number) => numfun = (h) => differentiate2(h, myPolynomial); function testDiffVal(aFunName: string, aFun: any, aValue: number, aExpected: number) { console.log(`Test ${aFunName}(${aValue}) = ${aFun(aValue)}, ` + `expected to be ~= ${aExpected}`); } testDiffVal("specDiff2OnH(myPolynomial)", specDiff2OnH(myPolynomial), 1, 6); testDiffVal("specDiff2OnF(0.01)", specDiff2OnF(0.01), 1, 6); // This solution uses anonymous functions to ease currying // It is perfectly possible to use functions instead. const differentiateCurry : (h: number) => (f: numfun) => (x: number) => number = (h) => (f) => (x) => (f(x + h) - f(x - h)) / (2 * h); // The good thing with curryfication if that we can specialize to // directly get the derivative as a function of `x` const specCurryCos: numfun = differentiateCurry(0.1)(Math.cos); testDiffVal("specCurryCos", specCurryCos, 0., 0.); testDiffVal("specCurryCos", specCurryCos, Math.PI, 0.); const specCurrySin: numfun = differentiateCurry(0.1)(Math.sin); testDiffVal("specCurrySin", specCurrySin, 0., 1.); testDiffVal("specCurrySin", specCurrySin, Math.PI, -1.); // Specialization so that it takes a function and returns a function // approximated with `h=0.1` const specCurryDiff: (f: numfun) => numfun = (f) => differentiateCurry(0.1)(f); // Same as const diff = differentiateCurry(0.1) testDiffVal("specCurryDiff(Math.cos)", specCurryDiff(Math.cos), Math.PI / 2, -1.); // Specialization so that it takes `h` and returns the derivative of // Math.cos approximated with `h` const specCurryOnH: (h: number) => numfun = (h) => differentiateCurry(h)(Math.cos); testDiffVal("specCurryOnH(0.05)", specCurryOnH(0.05), Math.PI / 2, -1.); testDiffVal("specCurryOnH(0.005)", specCurryOnH(0.005), Math.PI / 2, -1.); ////////// Exercice 2 ////////// // Type for pointed pairs type PointedPair = { car: T; cdr: U; }; // Functions on pointed pairs function cons(_car: T, _cdr: U) : PointedPair { return { car: _car, cdr: _cdr }; } function car(aCons: PointedPair) : T { return aCons['car']; } function cdr(aCons: PointedPair) : U { return aCons['cdr']; } // Type for lists type List = undefined | { car: T, cdr: List }; function isEmpty(aList : List): boolean { return aList === nil; } // Functions on lists const nil : undefined = undefined; function head(aList : List) : T { if (isEmpty(aList)) throw new Error("head: empty list"); else return car(aList as PointedPair>); } function tail(aList: List) : List { if (isEmpty(aList)) throw new Error("tail: empty list"); else return cdr(aList as PointedPair>); } // Some examples of lists let aNumberList : List = cons(1, cons(2, nil)); let aStringList : List = cons("a", cons("b", nil)); let aMixedList : List = cons("one", cons(2, nil)); let aMixedListMorePreciselyTyped : List = cons("one", cons(2, nil)); // A list that does not type // let anErroneousList : List = cons("true", nil); // Map the function `aFun` over the list `aList` function listMap(aFun : (arg: T) => U, aList : List) : List { if (isEmpty(aList)) return nil; // Cannot use `aList` because its type is incorrect else return cons(aFun(head(aList)), listMap(aFun, tail(aList))); } console.log(listMap((x) => x+1, aNumberList)); // Does not type // console.log(listMap((x) => x.toUpperCase(), aNumberList)); // List fold function listFoldR(aFun : (acc: Acc, el: T) => Acc, aInit: Acc, aList: List) : Acc { if (isEmpty(aList)) return aInit; else { return aFun(listFoldR(aFun, aInit, tail(aList)), head(aList)); } } console.log(listFoldR((acc, el) => acc+el, 0, aNumberList)); // Does not type // console.log(listFoldR((acc, el) => acc+el, 0, aStringList)); ////////// Exercice 3 ////////// // List of ranks of french characters in alphabetical order // This is only for a correct ordering of accents const charRanks : { [Identifier: string]: number }= Object.fromEntries( Array.from({length: 26}, (_, i) => [String.fromCharCode(i+65), i]) .concat(Array.from({length: 26}, (_, i) => [String.fromCharCode(i+97), i])) .concat([[ String.fromCharCode(201), 5 ]]) // accentuated E ); console.log(charRanks); // Alphabetical order comparison function on strings const compareStrings : (el1:string, el2:string) => number = (el1, el2) => { if ((el1 === "") && (el2 === "")) return 0; else { const diff = charRanks[el1[0]] - charRanks[el2[0]]; if (diff !== 0) return diff; else return compareStrings(el1.substring(1), el2.substring(1)); } }; // List of names sorted in alphabetical order const sortedNames : Array = db .map((el) => el.name) .sort(compareStrings); // Can be done without `compareStrings`, but the order is less satisfactory console.log(sortedNames); const compareBirths : (el1:Person, el2:Person) => number = (el1, el2) => el1.birth - el2.birth; // Person born the earliest const earliestBirth : Person = db .reduce((acc, el) => compareBirths(acc, el) < 0 ? acc : el, db[0]); // Can be done with sort(compareBirths)[0] but is less efficient console.log(earliestBirth); // Computes the longest life const reducer : (a: number,c:number) => number = (accumulator, currentValue) => Math.max(accumulator,currentValue); const longestLife = db .map((el) => el.death - el.birth) .reduce(reducer, 0); // don't use Math.max directly as a reducer console.log(longestLife); // Computes the list of person dead at an even age const deadEven : Array = db .filter((elt) => (elt.death - elt.birth) % 2 === 0); console.log(deadEven);