⎇ e3980d005f2096694d54da0f5f3fc76fac8ae914
crates/apps/yachay/src/main.rs
History
Who touched what
raw
feat(yachay): notebooks reproducibles — yachay-core + demo
1//! `yachay` — demostración de notebooks reproducibles.
2//!
3//! Arma un notebook con prosa, código y un embed de otro módulo
4//! brahman; imprime el orden de ejecución y el digest Merkle; luego
5//! edita una celda intermedia y muestra cómo la obsolescencia y el
6//! digest se propagan. `cargo run -p yachay`.
7
8use yachay_core::{CellId, CellKind, CellState, Notebook};
9
10/// Etiqueta de la clase de una celda.
11fn label(nb: &Notebook, id: CellId) -> &'static str {
12 match nb.cell(id).map(|c| &c.kind) {
13 Some(CellKind::Markdown) => "markdown",
14 Some(CellKind::Code { .. }) => "código ",
15 Some(CellKind::Embed { .. }) => "embed ",
16 None => "? ",
17 }
18}
19
20/// Primeros bytes de un digest, en hex — suficiente para distinguirlos.
21fn short(digest: Option<[u8; 32]>) -> String {
22 match digest {
23 Some(d) => d[..6].iter().map(|b| format!("{b:02x}")).collect(),
24 None => "—(ciclo)".to_string(),
25 }
26}
27
28fn main() {
29 let mut nb = Notebook::new();
30
31 let intro = nb.push(CellKind::Markdown, "# Cosecha de auyama\nAnálisis del rendimiento.");
32 let datos = nb.push(
33 CellKind::Code { language: "rust".into() },
34 "let kilos = vec![12.0, 18.0, 9.5, 21.0];",
35 );
36 let media = nb.push(
37 CellKind::Code { language: "rust".into() },
38 "let media = kilos.iter().sum::<f64>() / kilos.len() as f64;",
39 );
40 let grafico = nb.push(
41 CellKind::Embed { module: "pineal".into() },
42 "barras: kilos por semana",
43 );
44
45 // DAG: media depende de datos; el gráfico depende de ambos.
46 nb.add_dependency(media, datos);
47 nb.add_dependency(grafico, datos);
48 nb.add_dependency(grafico, media);
49
50 println!("\n yachay · notebook reproducible — {} celdas\n", nb.len());
51
52 println!(" orden de ejecución (según el DAG de dependencias):");
53 if let Some(order) = nb.execution_order() {
54 for (step, id) in order.iter().enumerate() {
55 println!(
56 " {}. [{}] celda {} digest {}",
57 step + 1,
58 label(&nb, *id),
59 id,
60 short(nb.digest(*id))
61 );
62 }
63 }
64
65 let digest_inicial = nb.notebook_digest();
66 println!("\n digest del notebook: {}", short(digest_inicial));
67
68 // Marca todo Fresh y luego edita la celda de datos.
69 for c in nb.cells().iter().map(|c| c.id).collect::<Vec<_>>() {
70 nb.set_state(c, CellState::Fresh);
71 }
72 println!("\n ── se edita la celda «datos» ──────────────────────────");
73 nb.set_source(datos, "let kilos = vec![12.0, 18.0, 9.5, 21.0, 30.0];");
74
75 println!("\n estado de las celdas tras la edición:");
76 for id in [intro, datos, media, grafico] {
77 let st = match nb.cell(id).unwrap().state {
78 CellState::Fresh => "fresca",
79 CellState::Stale => "OBSOLETA",
80 CellState::Failed => "fallida",
81 };
82 println!(" [{}] celda {} → {}", label(&nb, id), id, st);
83 }
84
85 println!("\n digest del notebook: {}", short(nb.notebook_digest()));
86 println!(
87 " {} la edición cambió el digest — la corrida anterior ya no\n \
88 es reproducible bit a bit; hay que re-ejecutar lo obsoleto.\n",
89 if digest_inicial != nb.notebook_digest() { "✔" } else { "✘" }
90 );
91}