⚙️ Teste et documente l'évaluation de formules

pull/20/head
Laurent Bossavit 2017-08-10 22:59:15 +02:00
parent 7d75d67d44
commit 1ce11035f0
2 changed files with 114 additions and 2 deletions

View File

@ -57,6 +57,9 @@
"daggy": "^1.1.0",
"eslint-plugin-react": "^7.0.1",
"express": "^4.15.3",
"fantasy-combinators": "0.0.1",
"fantasy-land": "^3.3.0",
"fantasy-tuples": "^1.0.0",
"file-loader": "^0.11.1",
"html-loader": "^0.5.1",
"img-loader": "^2.0.0",

View File

@ -103,7 +103,7 @@ describe('simplified tree walks', function() {
// Les helpers suivants rendent moins pénible la construction de valeurs
// notamment pour les tests
let num = x => Fx(Num(M.Just(x)))
let num = x => Fx(Num(x))
let add = (x, y) => Fx(Add(x,y))
let ref = (name) => Fx(Var(name))
@ -132,7 +132,7 @@ describe('simplified tree walks', function() {
// Celle-ci l'évaluation
const evaluator = state => a => {
return a.cata({
Num: (x) => x,
Num: (x) => M.Just(x),
Add: (x, y) => R.lift(R.add)(x,y),
Var: (name) => M.toMaybe(state[name]) // Doesn't typecheck
})
@ -249,4 +249,113 @@ describe('simplified tree walks', function() {
});
// Chapitre 3
// On sait evaluer des expressions, il faut aussi être capable de
// gérer les règles définissant les variables appelées dans ces
// expressions; voyons ce que ça donne avec un algèbre plus simple:
let calculate = R.curry((rules, name) => {
let find = (rules, name) => R.find(x => R.prop("name",x) == name,rules).expr,
expr = find(rules, name)
return fold(evaluator2(calculate(rules)), expr)
})
const evaluator2 = calculate => a => {
return a.cata({
Num: (x) => x,
Add: (x, y) => x+y,
Var: (name) => calculate(name)
})
}
it('should resolve variable dependencies', function() {
let rule1 = Assign("a",add(ref("b"),ref("b"))),
rule2 = Assign("b",num(15)),
rules = [rule1,rule2],
result = calculate(rules,"a")
expect(result).to.equal(30)
});
// Utilisons un Writer (un idiome fonctionnel pour par exemple écrire des logs)
// pour examiner le calcul de plus près:
const { of, chain, map, ap } = require('fantasy-land');
const { identity } = require('fantasy-combinators');
const { Tuple2 } = require('fantasy-tuples');
const Writer = M => {
const Writer = daggy.tagged(Writer,['run']);
Writer.of = function(x) {
return Writer(() => Tuple2(x, M.empty()));
};
Writer.prototype.chain = function(f) {
return Writer(() => {
const result = this.run();
const t = f(result._1).run();
return Tuple2(t._1, result._2.concat(t._2));
});
};
Writer.prototype.tell = function(y) {
return Writer(() => {
const result = this.run();
return Tuple2(null, result._2.concat(y));
});
};
Writer.prototype.map = function(f) {
return Writer(() => {
const result = this.run();
return Tuple2(f(result._1), result._2);
});
};
Writer.prototype.ap = function(b) {
return this.chain((a) => b.map(a));
};
return Writer;
};
const Str = daggy.tagged('Str',['s'])
Str.prototype.empty = Str.empty = function() {return Str("")}
Str.prototype.concat = function(b) {return Str(this.s+b.s)}
Str.prototype.length = function() {return this.s.length}
const StrWriter = Writer(Str)
StrWriter.prototype.toString = function() {return this.run()._2.s}
let trace = R.curry((rules, name) => {
let find = (rules, name) => R.find(x => R.prop("name",x) == name,rules).expr,
expr = find(rules, name)
return fold(tracer(trace(rules)), expr)
})
const tracer = recurse => a => {
return a.cata({
Num: (x) => StrWriter(() => Tuple2(x,x+",")),
Add: (x, y) => x.chain(xx => y.chain(yy => StrWriter(() => Tuple2(xx+yy,"+,")))),
Var: (name) => recurse(name).chain(x => StrWriter(() => Tuple2(x,name+",")))
})
}
// On voit qu'on a calculé la valeur de b 2 fois! Ce n'est pas utile,
// puisque cette valeur ne changera pas au cours du calcul; et comme on
// répète le calcul autant de fois qu'il y a de références à une variable
// donnée, si l'arbre est un tant soit peu complexe les performances seront
// très mauvaises.
it('should trace the shape of the computation', function() {
let rule1 = Assign("a",add(ref("b"),ref("b"))),
rule2 = Assign("b",num(15)),
rules = [rule1,rule2],
result = trace(rules,"a").run()
expect(result._2).to.equal("15,b,15,b,+,")
expect(result._1).to.equal(30)
});
});