Clojure : loading dependencies at the REPL

前端 未结 2 1973
忘了有多久
忘了有多久 2020-12-12 19:11

I recently learned (thanks to technomancy) that, at the REPL ---

This fails:

user=> (:require [clojure.set :as set])
java.lang         


        
相关标签:
2条回答
  • 2020-12-12 19:23

    The difference is that require is a function used for importing code, whereas :require is a keyword.

    Remember what happens when you use a keyword as a function:

    => (type :require)
    clojure.lang.Keyword
    => (:require {:abc 1 :require 14})
    14
    

    it looks itself up in the map. So when you pass [clojure.set :as set] to a keyword, it's trying to evaluate that to a vector, and fails because it doesn't know what clojure.set is. The Clojure docs say:

    Keywords implement IFn for invoke() of one argument (a map) with an optional second argument (a default value). For example (:mykey my-hash-map :none) means the same as (get my-hash-map :mykey :none).

    You may have been confused by the ns macro:

    (ns foo.bar
      (:refer-clojure :exclude [ancestors printf])
      (:require (clojure.contrib sql sql.tests))    ;; here's :require!
      (:use (my.lib this that))
      (:import (java.util Date Timer Random)
               (java.sql Connection Statement)))
    
    0 讨论(0)
  • 2020-12-12 19:41

    I'll go from high-level down to your particular problem:

    How Clojure (or LISPs) Generally Work

    REPLs, or Read-Eval-Print Loops are the core of how LISPs are designed:

    • The reader converts a stream of characters into data structures (called Reader Forms).
    • The evaluator takes collection of reader forms and evaluates them.
    • The printer emits the results of the evaluator.

    So when you enter text into a REPL, it goes through each of these steps to process your input and return the output to your terminal.

    Reader Forms

    First some, clojure reader forms. This will be extremely brief, I encourage you to read or watch (part 1, part 2) about it.

    A symbol in clojure is form that can represent a particular value (like a variable). Symbols themselves can be pass around as data. They are similar to pointers in c, just without the memory management stuff.

    A symbol with a colon in front of it is a keyword. Keywords are like symbols with the exception that a keyword's value are always themselves - similar to strings or numbers. They're identical to Ruby's symbols (which are also prefixed with colons).

    A quote in front of a form tells the evaluator to leave the data structure as-is:

    user=> (list 1 2)
    (1 2)
    user=> '(1 2)
    (1 2)
    user=> (= (list 1 2) '(1 2))
    true
    

    Although quoting can apply to more than just lists, it's primarily used for lists because clojure's evaluator will normally execute lists as a function-like invocation. Using the ' is shorthand to the quote macro:

    user=> (quote (1 2)) ; same as '(1 2)
    (1 2)
    

    Quoting basically specifies data structure to return and not actual code to execute. So you can quote symbols which refers to the symbol.

    user=> 'foo ; not defined earlier
    foo
    

    And quoting is recursive. So all the data inside are quoted too:

    user=> '(foo bar)
    (foo bar)
    

    To get the behavior of (foo bar) without quoting, you can eval it:

    user=> (eval '(foo bar)) ; Remember, foo and bar weren't defined yet.
    CompilerException java.lang.RuntimeException: Unable to resolve symbol: foo in this context, compiling:(NO_SOURCE_PATH:1)
    user=> (def foo identity)
    #'user/foo
    user=> (def bar 1)
    #'user/bar
    user=> (eval '(foo bar))
    1
    

    There's a lot more to quoting, but that's out of this scope.

    Requiring

    As for require statements, I'm assuming you found the former in the form of:

    (ns my.namespace
        (:require [clojure.set :as set]))
    

    ns is a macro that will transform the :require expression into the latter form you described:

    (require '[clojure.set :as set])
    

    Along with some namespacing work. The basics are described when asking for the docs of ns in the REPL.

    user=> (doc ns)
    -------------------------
    clojure.core/ns
    ([name docstring? attr-map? references*])
    Macro
      Sets *ns* to the namespace named by name (unevaluated), creating it
      if needed.  references can be zero or more of: (:refer-clojure ...)
      (:require ...) (:use ...) (:import ...) (:load ...) (:gen-class)
      with the syntax of refer-clojure/require/use/import/load/gen-class
      respectively, except the arguments are unevaluated and need not be
      quoted. (:gen-class ...), when supplied, defaults to :name
      corresponding to the ns name, :main true, :impl-ns same as ns, and
      :init-impl-ns true. All options of gen-class are
      supported. The :gen-class directive is ignored when not
      compiling. If :gen-class is not supplied, when compiled only an
      nsname__init.class will be generated. If :refer-clojure is not used, a
      default (refer 'clojure) is used.  Use of ns is preferred to
      individual calls to in-ns/require/use/import:
    

    REPL usage

    In general, don't use ns in the REPL, and just use the require and use functions. But in files, use the ns macro to do those stuff.

    0 讨论(0)
提交回复
热议问题