DSL en Go: transformamos tu código en un nuevo lenguaje
Imagine que está escribiendo un sistema de procesamiento de pedidos. Cada día los gerentes piden: «Agregue un 10% de descuento para todos los clientes de Moscú, pero solo si el monto del pedido es superior a 5000». Usted se mete en el código, corrige if-else, prueba, despliega. Una semana después, una nueva solicitud: «15% de descuento para VIP, si hay más de tres productos». ¿Le suena familiar?
¿Y si los gerentes pudieran escribir esas reglas ellos mismos? Sin su participación. Sin riesgo de romper la producción. Para eso existen los lenguajes específicos del dominio (DSL): lenguajes compactos, enfocados en una tarea concreta. Y Go, con su simplicidad y rendimiento, es ideal para crear dichos DSL.
En este artículo, analizaremos cómo crear DSL en Go, desde soluciones integradas simples hasta analizadores externos completos. Aprenderá a automatizar tareas rutinarias y brindar a los usuarios una herramienta que entenderán sin necesidad de programación.
¿Qué es un DSL y por qué es necesario en Go?
Un DSL (Domain-Specific Language) es un lenguaje de programación creado para un área específica. A diferencia de los lenguajes de propósito general (Go, Python, Java), un DSL resuelve una sola tarea, pero lo hace de manera elegante.
Ejemplos de la vida real:
- SQL — DSL para trabajar con bases de datos.
- Regex — DSL para buscar patrones en texto.
- HTML — DSL para el marcado de páginas web.
En Go, los DSL se utilizan para:
- Configuración — en lugar de YAML/JSON, se puede escribir lógica en su propio lenguaje.
- Reglas de negocio — permitir que los analistas cambien la lógica sin programadores.
- Pruebas — describir escenarios de forma legible.
- Procesamiento de datos — crear pipelines de transformación.
Go no fue elegido al azar: su compilador es rápido, su sintaxis es concisa y sus herramientas integradas (por ejemplo, text/scanner o go/ast) simplifican el análisis sintáctico. Pero lo principal es que Go permite crear tanto DSL internos (integrados en la sintaxis del propio Go) como DSL externos (con su propia sintaxis y analizador).
DSL internos en Go: usando la sintaxis del lenguaje
Los DSL internos son bibliotecas que parecen un lenguaje separado, pero utilizan la sintaxis de Go. No requieren escribir un analizador: simplemente escribe funciones que se «encadenan» en secuencias de llamadas.
Ejemplo: DSL para reglas de descuento
Supongamos que queremos que las reglas se vean así:
discount := Rule("Descuento VIP"). When(City("Moscú")). When(OrderSum > 5000). Apply(Discount(10))¿Cómo implementar esto en Go? Usamos el patrón Builder y funciones de orden superior:
package main
import "fmt"
// Rule - estructura de la reglatype Rule struct { Name string Checks []func(order map[string]interface{}) bool Discount int}
// When agrega una condiciónfunc (r *Rule) When(check func(order map[string]interface{}) bool) *Rule { r.Checks = append(r.Checks, check) return r}
// Apply establece el descuentofunc (r *Rule) Apply(discount int) *Rule { r.Discount = discount return r}
// Evaluate verifica si el pedido cumplefunc (r *Rule) Evaluate(order map[string]interface{}) bool { for _, check := range r.Checks { if !check(order) { return false } } return true}
// Funciones auxiliares de condiciónfunc City(city string) func(map[string]interface{}) bool { return func(order map[string]interface{}) bool { return order["city"] == city }}
func OrderSum(order map[string]interface{}) int { return order["sum"].(int)}
// Sobrecarga del operador > mediante clausurafunc GreaterThan(a func(map[string]interface{}) int, b