Ultima dată când am prezentat rapid ultimul meu pachet, {rixpress}dar cred că pentru a înțelege cu adevărat ce {rixpress} aduce la masă, trebuie să rezolvi aceeași problemă fără ea. Și, întâmplător, cred că acest exercițiu arată și ceea ce îl face pe Nix de fapt atât de bun.
Scopul este de a construi o conductă de știință a datelor. Exemplul de aici este pur ilustrativ și compară o abordare bazată pe Nix cu o abordare care nu este bazată pe Nix. Așadar, am construit același model poliglot Real Business Cycle de două ori. În primul rând, am făcut-o fără {rixpress} (nici {rix}), folosind o combinație de Docker, Make și o grămadă de scripturi wrapper. Apoi, am făcut-o cu {rix} şi {rixpress}.
Ambele conducte produc exact același rezultat. Dar modalitatea de a ajunge acolo este fundamental diferită.
Jongler cu instrumente imperative
Fără Nix, trebuie să utilizați manageri de pachete și instrumente specifice limbii pentru a configura mai întâi mediul. Deci, pentru Python am folosit uv (ceea ce este fantastic să fiu sincer), apoi să instalez versiunea corectă de R pe care am folosit-o rig și un instantaneu Posit CRAN pentru pachete și pentru Julia, pur și simplu am descărcat un pachet pre-compilat al versiunii de care aveam nevoie și am folosit managerul de pachete încorporat pentru a instala și versiuni specifice de pachete.
De asemenea, pentru a face față dependențelor la nivel de sistem, am grupat totul într-o imagine Docker. Aceasta este o schiță a Dockerfile:
# Add R repository and install specific version
RUN apt-get update && apt-get install -y software-properties-common
RUN add-apt-repository ppa:...
RUN curl -L https://rig.r-pkg.org/... | sh
RUN rig add 4.5.1
# Install Python with uv
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
RUN uv python install 3.13
# Download and extract Julia
RUN curl -fsSL https://julialang-s3.julialang.org/... -o julia.tar.gz
RUN tar -xzf julia.tar.gz -C /opt/
# Install packages for each language separately
RUN echo 'options(repos = c(CRAN = ...))' > /root/.Rprofile
RUN Rscript -e 'install.packages(...)'
# Install Python packages using uv with specific versions for reproducibility.
RUN echo "pandas==2.3.3" > /tmp/requirements.txt && \
echo "scikit-learn==1.7.2" >> /tmp/requirements.txt && \
# ... more packages ...
RUN uv pip install --no-cache -r /tmp/requirements.txt && \
rm /tmp/requirements.txt
# Install specific versions of Julia packages for reproducibility
RUN julia -e 'using Pkg; \
Pkg.add(name="Arrow", version="2.8.0"); \
Acest tradiţional abordarea se simte ca și cum ai fi mai întâi un administrator de sistem și, în al doilea rând, un om de știință de date. The Dockerfile este un script lung, pas cu pas, imperativ al comenzilor shell. Trebuie să scrii Cum lucrurile trebuie instalate, iar acest lucru, desigur, variază pentru fiecare limbă. Fiecare limbă are nevoie de propriul tratament special, propria sa comandă de instalare a pachetului și propriul set de dependențe. De exemplu, pentru Python, chiar aveam nevoie de mai multă configurație decât ceea ce am arătat mai sus:
# Ensure the installed binary is on the `PATH`
ENV PATH="/root/.local/bin/:$PATH"
# Install the specified Python version using uv.
RUN uv python install ${PYTHON_VERSION}
# Setup default virtual env
RUN uv venv /opt/venv
# Use the virtual environment automatically
ENV VIRTUAL_ENV=/opt/venv
# Place entry points in the environment at the front of the path
ENV PATH="/opt/venv/bin:$PATH"
Acest lucru se datorează faptului că trebuia să setez mediul virtual instalat de uv ca cea care va fi folosită implicit. Acest lucru este în regulă în Docker, dar nu este ceva ce probabil ați dori să faceți pe o mașină reală. Finala Dockerfile pentru că exemplul nostru „simplu” avea peste 100 de rânduri (inclusiv comentarii).
Acum că mediul este spus, trebuie de fapt să orchestrăm fluxul de lucru. am folosit Make pentru aceasta, ceea ce înseamnă a scrie a Makefile. Sincer, în zilele noastre, datorită LLM-urilor, asta nu este atât de mare o problemă. Dar înainte de LLM, ar fi destul de enervant, deoarece trebuie să definiți manual ce fișier depinde de ce alt fișier. Iată cum arată:
# ==============================================================================
# Makefile for the Polyglot RBC Model Pipeline
# ==============================================================================
# Define the interpreters for each language.
JULIA := julia
PYTHON := python
RSCRIPT := Rscript
QUARTO := quarto
# Define directory variables for better organization.
DATA_DIR := data
PLOTS_DIR := plots
REPORT_DIR := report
FUNCTIONS_DIR := functions
# Define the final and intermediate data files.
SIMULATED_DATA := $(DATA_DIR)/simulated_rbc_data.arrow
PREDICTIONS := $(DATA_DIR)/predictions.arrow
FINAL_PLOT := $(PLOTS_DIR)/output_plot.png
FINAL_REPORT := $(REPORT_DIR)/readme.html
# --- Main Rules ---
# The default 'all' rule now points to the final compiled HTML report.
all: $(FINAL_REPORT)
# Rule to render the final Quarto report.
# Depends on the Quarto source file and the plot from the R step.
$(FINAL_REPORT): readme.qmd $(FINAL_PLOT) | $(REPORT_DIR)
@echo "--- (Quarto) Compiling final report ---"
$(QUARTO) render $< --to html --output-dir $(REPORT_DIR)
... and so on ...
Sunt încă 65 de versuri pentru orchestrație.
În cele din urmă, și probabil cel mai rău dintre toate, este că ajungi să scrii tone de „cod adeziv”. Deoarece make rulează doar scripturi, fiecare pas al analizei dumneavoastră (simularea Julia, antrenamentul Python) trebuie să fie înglobat într-un script care nu face altceva decât să analizeze argumentele liniei de comandă, să citească un fișier de intrare, să apeleze real funcția de analiză și scrieți un fișier de ieșire. Este mult cod doar pentru a face lucrurile să vorbească între ele.
Nix: Declarativ, simplu și curat
Nix face tot acest proces mult mai ușor, de fapt nu este nici măcar corect. În loc să spui computerului Cum pentru a face totul, doar declari ce vrei tu. Îți descrii cerințele, iar Nix își dă seama de restul. Dar pentru că nu este atât de ușor să intri în Nix, am scris {rix} şi {rixpress} pachete ca interfețe de nivel înalt pentru puterea lui Nix.
De exemplu, pentru a configura mediul, trebuie doar să enumerați pachetele R, Python și Julia de care aveți nevoie și {rix} se ocupă de orice altceva. Îți dă seama cum să le instalezi, rezolvă toate dependențele la nivel de sistem și generează expresia complexă Nix pentru tine. Nu trebuie să fii administrator de sistem; trebuie doar să știi ce pachete necesită analiza ta. Acest lucru se datorează faptului că toate administrare de sistem munca a fost gestionată în amonte de întreținerii pachetelor Nix (MVP-uri reale); Menținerii Nix codifică rețetele de construcție, graficele de dependență și patch-urile necesare pentru fiecare pachet, astfel încât să nu fie nevoie. (Îmi amintește de acest citat din Jenny Bryan: Desigur, cineva trebuie să scrie pentru bucle. Nu trebuie să fii tudar aici este un cod Nix lipsit de farmec pentru a face pachetele să funcționeze bine în loc de bucle.)
Iată ce gen-env.R scriptul arată astfel:
rix(
date = "2025-10-14",
r_pkgs = c("ggplot2", "dplyr", "arrow"),
jl_conf = list(jl_version = "lts", ...),
py_conf = list(py_version = "3.13", ...),
...
)
Apoi, pentru conductă, este aceeași poveste. Scrieți doar ceea ce aveți nevoie, nu cum se face. Nix se poate descurca cu asta. Iată ce gen-pipeline.R scriptul arată astfel:
// gen-pipeline.R - a small part list( rxp_jl(name = simulated_rbc_data, expr = "simulate_rbc_model(...)"), rxp_py(name = predictions, expr = "train_model(simulated_rbc_data)"), rxp_r(name = output_plot, expr = "plot_predictions(predictions)"), ... )
Dependențele sunt deduse automat. {rixpress} vede asta predictions folosește simulated_rbc_data obiect și știe să execute mai întâi pasul Julia. Se ocupă de toate I/O-urile și pentru tine. Obiectele sunt serializate și neserializate în mod transparent pentru dvs.
Codul tău științific trăiește acum în funcții pure, fără orice analiză din linia de comandă sau I/O de fișiere. Vă puteți concentra în întregime pe analiză.
Cea mai mare diferență nu este doar simplitatea; este garantia. Abordarea Docker vă oferă reproductibilitate astăzi. Dar peste un an, dacă reconstruiești Dockerfileimaginile de bază mutabile și dependențele de pachete mutabile înseamnă că s-ar putea să obțineți un mediu subtil diferit. Imaginea Docker de bază se va schimba și, în câțiva ani, va înceta complet să funcționeze (Ubuntu 24.04, care este destul de des folosit ca imagine de bază, va ajunge la sfârșitul vieții în 2029).

