Namespace es el nombre del mecanismo de organización de código utilizado en varios lenguajes de programación. Éstos permiten la agrupación lógica de variables, funciones y estructuras del lenguaje en cuestión para fomentar la reutilización y jerarquización del código de una aplicación.

A continuación nos familiarizaremos con la forma en la que Clojure maneja la organización de código.

REPL

Para comenzar, iniciaremos creando una aplicación con Leiningen.

$ lein new app kitchen
Generating a project called kitchen based on the 'app' template.

$ tree kitchen
kitchen
├── CHANGELOG.md
├── LICENSE
├── README.md
├── doc
│   └── intro.md
├── project.clj
├── resources
├── src
│   └── kitchen
│       └── core.clj
└── test
    └── kitchen
        └── core_test.clj

6 directories, 7 files

Con esto acabamos de crear una aplicación llamada kitchen que utilizaremos para crear algunos archivos donde agruparemos variables y funciones que varían en contexto.
Para esto crearemos dos archivos:

  • kitchen/src/kitchen/breakfast.clj
(ns kitchen.breakfast)

(def dishes #{:french-toast :benedict-eggs :omelette})
  • kitchen/src/kitchen/lunch.clj
# lunch.clj

(ns kitchen.lunch)

(def dishes #{:ribeye-steak :kebabs :italian-pizza})

Ahora iniciaremos una sesión en la REPL para acceder a los diferentes namespaces de esta aplicación.

$ lein repl
nREPL server started on port 60120 on host 127.0.0.1 - nrepl://127.0.0.1:60120
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_92-b14
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

kitchen.core=>

Perfecto. Ahora intentaremos acceder a los platillos disponibles para desayunos:

kitchen.core=> kitchen.breakfast/dishes

CompilerException java.lang.ClassNotFoundException: kitchen.breakfast [...]

Mmmm. Parece ser que nuestra sesión no tiene noción de ese namespace.
Para tener acceso a ese código usaremos require el cual nos permite referenciar código dentro de ese namespace.

kitchen.core=> (require 'kitchen.breakfast)
nil
kitchen.core=> kitchen.breakfast/dishes
#{:french-toast :benedict-eggs :omelette}

Excelente. Podemos hacer lo mismo para disponer de kitchen.lunch ejecutando (require 'kitchen.lunch).

Ambas archivos contienen su propio significado de la variable dishes, por lo que es imperativo que hagamos referencia al full path de éstos para acceder a la variable que queremos.

ns

Es claro que contamos con 3 namespaces actualmente:

  • kitchen.core
  • kitchen.breakfast
  • kitchen.lunch

La REPL por defacto nos pone en kitchen.core, en el dado caso de que queramos explorar las propiedades de un namespace en particular, podemos utilizar la función ns la cual nos cambia al namespace que mandamos como parámetro o en caso de que ese namespace no exista, lo crea.

kitchen.core=> (ns kitchen.breakfast)
nil
kitchen.breakfast=> dishes
#{:french-toast :benedict-eggs :omelette}
kitchen.breakfast=>

Podemos darnos cuenta que el prefijo del prompt refleja el namespace en el que la REPL se encuentra actualmente. Eso significa que tenemos total acceso a todas las variables y funciones contenidas en ese namespace en específico.

La función ns no solo nos permite cambiar de namespace, tambien nos permite definir específicamente el criterio con el cual podemos cargamos un namespace en particular. En el siguiente ejemplo crearemos un nuevo namespace llamado kitchen.dinner en el cual importaremos platillos disponibles en kitchen.lunch utilizando un alias.

; 1. De `kitchen.breakfast`, nos cambiaremos y por ende crearemos un nuevo namespaces
; `kitchen.dinner` que a su vez importará `kitchen.lunch` bajo el alias `lunch`.
kitchen.breakfast=> (ns kitchen.dinner (:require [kitchen.lunch :as lunch]))
nil

; Aquí podemos ver que efectivamente tenemos acceso a los platillos de la comida
; por medio del alias `lunch`. Esto nos permite definir nuestros propios platillos
; sin ocasionar una colisión entre la comida y la cena.
kitchen.dinner=> lunch/dishes
#{:ribeye-steak :kebabs :italian-pizza}

kitchen.dinner=> (def dishes (conj lunch/dishes :cheese-platter))
#'kitchen.dinner/dishes

kitchen.dinner=> dishes
#{:ribeye-steak :cheese-platter :kebabs :italian-pizza}

use

Clojure soporta otra función llamada use. Esta nos permite tomar las variables y funciones dentro de un namespace y traerlos a nuestro namespace. Esto en la práctica puede ser peligroso, ya que si existen variables o funciones con los mismos nombres corremos el riesgo de sobreescribirlos lo cual puede causar resultados inesperados.

use soporta algunas opciones como :exclude, :only y :rename que te permiten tener un mayor control sobre que importas, que no y de que forma.

require soporta lo mismo que use por medio de la opción refer.

; Estos comandos son equivalentes
(require '[kitchen.lunch :refer :all])

(use 'kitchen.lunch)

; Ambos te permiten utilizar `kitchen.lunch/dishes` directamente como `dishes`

Hoy en día, la comunidad de Clojure recomienda usar require ya que proporciona mayor funcionalidad que use en todos los aspectos.

[1] https://en.wikipedia.org/wiki/Namespace