A Clojure programozási nyelv

Névterek

Ha feltelepítünk egy Clojure REPL-t és megnyitjuk, a megjelenő felületen az első, amit látunk, ez lesz:

    user=>

...ahol a "user" szimbólum jelzi az aktuális névtér nevét. A névtér a clojure.lang.Namespace osztály egy objektuma, és lényegében egy leképezésként szolgál: szimbólumokat képez le Var-okra és osztályokra. A programunk futása során több névtér objektum is lehet a memóriában, az aktuális névtér nevét az *ns*-sel kérdezhetjük le, az összes névtér objektum listáját az (all-ns) hívással kapjuk meg. Számos módszer létezik új névterek létrehozására, ilyenek az in-ns, a create-ns és az ns makró. Ezek közül a create-ns a legritkábban használt, legalacsonyabb szintű módszer:

    user=> (def v (create-ns 'nevter))   ; a v var-ba belerakunk egy névtér objektumot
    #'user/v

    user=> (class v)
    clojure.lang.Namespace

A create-ns egy teljesen üres névteret hoz létre, ami azt jelenti, hogy a fenti 'nevter nevű névterünkből nem hivatkozhatunk semmilyen osztályra a clojure.core és a java.lang csomagokból. Ezzel szemben az in-ns a névterek közötti navigációra illetve névterek létrehozására is használható - az in-ns tehát a következőket teszi: "névteret vált" (a megadott nevű névtérre állítja az aktuális névteret jelző *ns* értéket), illetve ha addig még nem volt olyan névtér, amire váltani akarunk, akkor létrehoz egyet. Az in-ns-sel létrehozott névtér hivatkozik a java.lang csomagra, de a clojure.core-ra nem.

    user=> (in-ns 'nevter2)
    
    #<Namespace nevter2>
    
    nevter2=> (reduce + '(1 2 3 4))       ; az in-ns-sel létrehozott névtér nem hivatkozik a clojure.core-ra
    
    java.lang.Exception: Unable to resolve symbol: reduce in this context (NO_SOURCE_FILE:11)

A REPL-ben való kísérletezgéshez inkább az in-ns használatát ajánlják, a forráskódfájlokban való névtérdefiníciókhoz viszont a később bemutatásra kerülő ns makrót.

Felmerül a kérdés, hogy amennyiben az előbb látott in-ns-sel hozunk létre egy névteret, hogyan hivatkozhatunk mégis a clojure.core csomagban lévő névterekre? Erre ad választ a require:

    user=> (require 'clojure.string '[clojure.test :as teszt])    ; lehetne ['clojure.test :as 'teszt] is...
    nil

    user=> (require '(clojure [string :as string] test)) ; előzővel ekvivalens: mivel mindkét névtér a clojure-höz tartozik, ezért azt kiemeljük
    nil

    user=> (teszt/is (= 4 4))  ; az is függvény a clojure.test névtérben helyezkedik el
    true

    user=> (clojure.string/replace "alma" "a" "afa")    ; a replace pedig a clojure.string-ben
    "afalmafa"

Látható, hogy a require-rel betöltött névterek függvényeit csak a teljesen minősített nevükkel, a névtér/függvény vagy alias/függvény formátumban lehet elérni a require hívás után. De mi történik a háttérben? A require-nek megadható egy :verbose flag is, ami pontosan mutatja, valójában mi is zajlik a háttérben. Frissen megnyitott REPL-be írjuk be:

    user=> (require 'clojure.string '[clojure.test :as teszt] :verbose)
    
    (clojure.core/load "/clojure/test")
    (clojure.core/load "/clojure/template")
    (clojure.core/load "/clojure/walk")
    (clojure.core/in-ns 'clojure.template)
    (clojure.core/alias 'walk 'clojure.walk)
    (clojure.core/in-ns 'clojure.test)
    (clojure.core/alias 'temp 'clojure.template)
    (clojure.core/load "/clojure/stacktrace")
    (clojure.core/in-ns 'clojure.test)
    (clojure.core/alias 'stack 'clojure.stacktrace)
    (clojure.core/in-ns 'user)
    (clojure.core/alias 'teszt 'clojure.test)
    nil

    user=> (require 'clojure.string '[clojure.test :as teszt] :verbose)
    
    (clojure.core/in-ns 'user)
    (clojure.core/alias 'teszt 'clojure.test)
    nil

Két dolgot vehetünk észre: egyrészt, ha egy require-rel kétszer töltünk be egy névteret, azt valójában csak egyszer tölti be a rendszer (load). A második észrevétel az, hogy ha egy olyan névteret töltünk be a require-rel, ami maga is tartalmaz egy require hívást, akkor egyúttal azokat a névtereket is betölti (fenti példában így töltődött be a clojure.template, clojure.stacktrace, stb.) Persze rendelkezhetünk úgy is, hogy a rendszer az adott require hívásban újratöltse a paraméterként megadott névtereket, ebben az esetben a :reload flag-et kell kiírnunk, a :verbose flag-hez hasonlóan.

Említettük, hogy a require hívás után a névtér/függvénynév formátumban lehet hívni a betöltött névtér függvényeit. Ez lehet, hogy kényelmetlen, még alias-ok használatával is, ezért bevezették a use-t:

    user=> (use '[clojure.string :as str :only [join split]])   ; az :as str rész elhagyható (nem kötelező névtér-alias-t adni)
    nil

    user=> (split "a,b,c,d" #",")
    ["a" "b" "c" "d"]
    
    user=> (str/split "a,b,c,d" #",")   ; de ha adtunk a névtérnek alias-t, így is hívhatjuk
    ["a" "b" "c" "d"]

A fenti példában az :only kapcsoló jelentősége az, hogy csak a join és a split függvényekre hivatkozhatunk kizárólag a függvények nevét használva. Az :only kapcsoló használata nélkül a kód gyakran átláthatatlanná válhat, mert a különböző névterekben lévő függvények elfedhetik egymást, ezért a use hívásoknál nagyon javasolt az :only kapcsoló használata. Egy másik lehetőség az :exclude kapcsoló, amivel az :exclude-ban megadott függvények kivételével a névtérben szereplő minden függvényt használhatunk teljesen minősített név nélkül, azonban hosszú távon ez a módszer sem garantálhatja a névütközés-mentességet.

Mit tehetünk akkor, ha két különböző névtérben lévő függvénynek ugyanaz a neve? A use megengedi a függvények "átnevezését":

    user=> (use '[clojure.string :rename {replace str-replace, reverse str-reverse})

A require és a use használata után az ns makró használata már természetes lesz, hiszen ez a makró egyesíti a require és a use makrók használatát. Lássunk egy összetett példát:

    (ns nevter
        "egy bonyolult nevter"                                    ; dokumentációs sztring
        (:refer-clojure :exclude [defstruct])                     ; a :refer-clojure elhagyható, ekkor a (refer 'clojure) szerinti viselkedés az alapértelmezett
        (:use (clojure set xml))
        (:use [clojure.test :as teszt :only (are is)] :reload)
        (:require (clojure [zip :as z]) 
                  [clojure.inspector :as insp]                    ; a require libspec-eket az ns makrón belül nem kell idézőjelezni
                  (clojure template walk) :verbose)
        (:import (java.util Date GregorianCalendar)               ; ezeket a Java osztályokat a teljesen minősített nevük nélkül lehet ezután használni
                 (java.io File)))