2017-07-14 15:20:38 +00:00
|
|
|
import R from 'ramda'
|
|
|
|
import {expect} from 'chai'
|
|
|
|
import daggy from 'daggy'
|
2017-07-18 09:38:55 +00:00
|
|
|
import {Maybe as M} from 'ramda-fantasy'
|
2017-07-14 15:20:38 +00:00
|
|
|
|
|
|
|
describe('simplified tree walks', function() {
|
|
|
|
|
|
|
|
// Notre domaine peut se simplifier à une liste d'équations à trous:
|
|
|
|
// a: 45
|
|
|
|
// b: a + c
|
|
|
|
// d: a + 4
|
|
|
|
// e: b + d
|
|
|
|
// Disons que je veux connaitre "e", alors il va me manquer "c"
|
|
|
|
// Si je connais "c", alors je peux calculer "e"
|
|
|
|
// Et mon ambition est aussi de pouvoir visualiser le calcul en HTML
|
|
|
|
// Donc j'ai une structure plate que je transforme en arbre (ce n'est pas
|
|
|
|
// le focus de la présente exploration), je veux pouvoir demander des choses
|
|
|
|
// diverses à cet arbre: l'évaluer, repérer les trous, le transformer en HTML
|
|
|
|
|
|
|
|
// Plus tard je vais avoir des trucs plus sophistiqués, par exemple:
|
|
|
|
// b: a + (bleu: b, vert: c)
|
|
|
|
// qui est équivalent à:
|
|
|
|
// b: b-bleu + b-vert
|
|
|
|
// b-bleu: a + b
|
|
|
|
// b-vert: a + c
|
|
|
|
// Le but du jeu est de pouvoir le représenter de façon compacte, mais
|
|
|
|
// d'avoir un arbre simple à manipuler
|
|
|
|
|
2017-08-01 08:42:11 +00:00
|
|
|
// Pour intégrer dans le simulateur, il faut remplir les exigences
|
|
|
|
// suivantes:
|
|
|
|
// - décorer l'arbre avec une valeur à chaque noeud
|
|
|
|
// - réaliser le calcul de façon efficiente (1 fois par variable)
|
|
|
|
// - savoir "court-circuiter" le calcul de variables manquantes dans les conditionnelles
|
|
|
|
// - avoir un moyen de gérer les composantes et filtrage
|
|
|
|
|
2017-07-18 07:20:58 +00:00
|
|
|
const Fx = daggy.tagged('Fx',['x'])
|
|
|
|
const unFix = R.prop('x')
|
|
|
|
|
2017-08-01 13:35:43 +00:00
|
|
|
// Chaque élément de notre liste est une définition:
|
|
|
|
|
|
|
|
const Def = daggy.taggedSum('Def', {
|
|
|
|
Formula: ['expr'],
|
|
|
|
Conditional: ['cond','expr'], // Applicable si
|
|
|
|
Blocked: ['cond','expr'], // Non applicable si
|
|
|
|
})
|
|
|
|
const {Formula, Conditional, Blocked} = Def
|
|
|
|
|
|
|
|
// Ce qu'on décrit est un framework de programmation déclarative: on stipule des
|
|
|
|
// définitions (salaire net = brut - cotisations) mais on les donne sans ordre
|
|
|
|
// impératif, on laisse au moteur le soin de calculer les dépendances
|
|
|
|
|
|
|
|
// Par contre, à l'exécution, il faut bien calculer des "effets de bord"
|
|
|
|
// pour rester performant: chaque évaluation d'une définition doit mettre
|
|
|
|
// à jour le 'dictionnaire' des valeurs connues, puis le mettre à disposition
|
|
|
|
// de la suite du calcul
|
|
|
|
|
|
|
|
// La partie droite d'une définition est une expression:
|
|
|
|
|
2017-07-18 07:20:58 +00:00
|
|
|
const Expr = daggy.taggedSum('Expr',{
|
|
|
|
Num: ['x'],
|
|
|
|
Add: ['x', 'y'],
|
|
|
|
Var: ['name']
|
2017-08-01 08:42:11 +00:00
|
|
|
// NotIf: ['condition','formule'],
|
|
|
|
// AnyOf: ['conditions'],
|
|
|
|
// AllOf: ['conditions'],
|
2017-07-14 15:20:38 +00:00
|
|
|
})
|
2017-08-01 13:35:43 +00:00
|
|
|
const {Num, Add, Var} = Expr
|
2017-07-18 07:20:58 +00:00
|
|
|
|
|
|
|
// fold :: Functor f => (f a -> a) -> Fix f -> a
|
|
|
|
const fold = R.curry((alg, x) => R.compose(alg, R.map(fold(alg)), unFix)(x))
|
2017-07-14 15:20:38 +00:00
|
|
|
|
2017-07-18 07:20:58 +00:00
|
|
|
// Cette fonction fournit la traversée
|
|
|
|
Expr.prototype.map = function(f) {
|
2017-07-14 15:20:38 +00:00
|
|
|
return this.cata({
|
2017-07-18 07:20:58 +00:00
|
|
|
Num: (x) => this, // fixed
|
2017-07-18 07:26:56 +00:00
|
|
|
Add: (x, y) => Add(f(x), f(y)),
|
|
|
|
Var: (name) => this
|
2017-07-14 15:20:38 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-07-18 07:20:58 +00:00
|
|
|
// Celle-ci l'évaluation
|
2017-07-18 07:26:56 +00:00
|
|
|
const evaluator = state => a => {
|
2017-07-18 07:20:58 +00:00
|
|
|
return a.cata({
|
|
|
|
Num: (x) => x,
|
2017-07-18 09:38:55 +00:00
|
|
|
Add: (x, y) => R.lift(R.add)(x,y),
|
|
|
|
Var: (name) => M.toMaybe(state[name]) // Doesn't typecheck
|
2017-07-14 15:20:38 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-07-18 09:44:05 +00:00
|
|
|
// Celle-ci la collecte des variables manquantes
|
|
|
|
const collector = state => a => {
|
|
|
|
return a.cata({
|
|
|
|
Num: (x) => [],
|
|
|
|
Add: (x, y) => R.concat(x,y),
|
|
|
|
Var: (name) => state[name] ? [] : [name]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-07-18 09:38:55 +00:00
|
|
|
let evaluate = (expr, state={}) =>
|
|
|
|
fold(evaluator(state), expr)
|
|
|
|
.getOrElse(null) // for convenience
|
2017-07-18 07:20:58 +00:00
|
|
|
|
2017-07-18 09:44:05 +00:00
|
|
|
let missing = (expr, state={}) =>
|
|
|
|
fold(collector(state), expr)
|
|
|
|
|
2017-07-18 09:38:55 +00:00
|
|
|
let num = x => Fx(Num(M.Just(x)))
|
2017-07-18 07:20:58 +00:00
|
|
|
let add = (x, y) => Fx(Add(x,y))
|
2017-07-18 07:26:56 +00:00
|
|
|
let ref = (name) => Fx(Var(name))
|
2017-07-18 07:20:58 +00:00
|
|
|
|
2017-07-14 15:20:38 +00:00
|
|
|
it('should provide a protocol for evaluation', function() {
|
2017-07-18 07:20:58 +00:00
|
|
|
let tree = num(45),
|
2017-07-14 21:11:53 +00:00
|
|
|
result = evaluate(tree)
|
2017-07-14 15:20:38 +00:00
|
|
|
expect(result).to.equal(45)
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should evaluate expressions', function() {
|
2017-07-18 07:20:58 +00:00
|
|
|
let tree = add(num(45),num(25)),
|
2017-07-14 21:11:53 +00:00
|
|
|
result = evaluate(tree)
|
2017-07-14 15:20:38 +00:00
|
|
|
expect(result).to.equal(70)
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should evaluate nested expressions', function() {
|
2017-07-18 07:20:58 +00:00
|
|
|
let tree = add(num(45),add(num(15),num(10))),
|
2017-07-14 21:11:53 +00:00
|
|
|
result = evaluate(tree)
|
2017-07-14 15:20:38 +00:00
|
|
|
expect(result).to.equal(70)
|
|
|
|
});
|
|
|
|
|
2017-07-18 07:26:56 +00:00
|
|
|
it('should evaluate expressions involving variables', function() {
|
|
|
|
let tree = add(num(45),ref("a")),
|
|
|
|
result = evaluate(tree,{a:25})
|
|
|
|
expect(result).to.equal(70)
|
|
|
|
});
|
|
|
|
|
2017-07-18 09:38:55 +00:00
|
|
|
it('should evaluate expressions involving missing variables', function() {
|
|
|
|
let tree = add(num(45),ref("b")),
|
|
|
|
result = evaluate(tree,{a:25})
|
|
|
|
expect(result).to.equal(null)
|
|
|
|
});
|
|
|
|
|
2017-07-14 15:20:38 +00:00
|
|
|
it('should provide a protocol for missing variables', function() {
|
2017-07-18 09:44:05 +00:00
|
|
|
let tree = ref("a"),
|
2017-07-14 21:11:53 +00:00
|
|
|
result = missing(tree)
|
2017-07-14 15:20:38 +00:00
|
|
|
expect(result).to.deep.equal(["a"])
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should locate missing variables in expressions', function() {
|
2017-07-18 09:44:05 +00:00
|
|
|
let tree = add(num(45),ref("a")),
|
2017-07-14 21:11:53 +00:00
|
|
|
result = missing(tree)
|
2017-07-14 15:20:38 +00:00
|
|
|
expect(result).to.deep.equal(["a"])
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should locate missing variables in nested expressions', function() {
|
2017-07-18 09:44:05 +00:00
|
|
|
let tree = add(add(num(35),ref("a")),num(25)),
|
2017-07-14 21:11:53 +00:00
|
|
|
result = missing(tree)
|
2017-07-14 15:20:38 +00:00
|
|
|
expect(result).to.deep.equal(["a"])
|
|
|
|
});
|
2017-07-18 09:44:05 +00:00
|
|
|
|
|
|
|
it('should locate missing variables in nested expressions', function() {
|
|
|
|
let tree = add(add(num(35),ref("a")),num(25)),
|
|
|
|
result = missing(tree,{a:25})
|
|
|
|
expect(result).to.deep.equal([])
|
|
|
|
});
|
|
|
|
|
2017-07-14 15:20:38 +00:00
|
|
|
});
|