Skip to content

Commit

Permalink
Nailing down namespace semantics a bit more.
Browse files Browse the repository at this point in the history
  • Loading branch information
daveray committed Oct 17, 2011
1 parent 03bd4d2 commit 3763fb3
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 36 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Make a future:

# Primes example from clojure docs
# http://clojuredocs.org/clojure_core/clojure.core/reduce
x = Familiar.with do
Familiar.with do
reduce fn { |primes,number|
if some(self[:zero?], map(fn {|x| number % x}, primes))
primes
Expand All @@ -153,7 +153,6 @@ Make a future:
vector(2),
take(100, iterate(self[:inc], 3))
end
Familiar.println x
=> [2 3 5 7 11 13 17 19 23 29 31 ... 67 71 73 79 83 89 97 101]

## License
Expand Down
121 changes: 89 additions & 32 deletions lib/familiar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,39 @@
require "clojure-1.3.0.jar"

module Familiar
# Represents a Clojure namespace.
# Represents a Clojure namespace. Don't create directly. Use
# Familiar.ns(...)
#
# All methods calls map to calling the same var in this namespace as
# a function. Underscores are automatically converted to hyphens.
#
# See:
# Familiar.ns()
class NS
def initialize(ns)
@ns = ns
end

# Require this namespace so functions and vars are accessible.
# TODO should this be automatic?
def require
Familiar["clojure.core", :require].invoke(Familiar.symbol(@ns))
#
# Returns self
def require(*args)
#puts "Requiring #{@ns}"
r = Familiar[:require]
if args.empty?
r.invoke(Familiar.symbol(@ns))
else
r.invoke(*args)
end
self
end

# Lookup a var in this namespace
# Lookup a var in this namespace.
#
# Examples:
#
# Familiar.ns("clojure.java.io")[:reader]
def [] (var)
m = Java::clojure.lang.RT.var(@ns, var.to_s.gsub("_", "-"))
m.is_bound? ? m : nil
Expand All @@ -33,55 +53,97 @@ def [] (var)
# All methods calls map to calling the same var in this namespace as
# a function. Underscores are automatically converted to hyphens.
def method_missing(meth, *args, &block)
#puts "Missing #{@ns}/#{meth}"
m = self[meth]
if m
m.invoke(*args)
else
super
end
end

# TODO This gets in the way of clojure.core/eval. One fix might be
# NS < BasicObject. Not sure yet.
undef eval
end

# Provides access to Clojure vars for when you need to use a Clojure
# var without invoking it.
# Returns a Clojure NS by name. When given no argument, returns clojure.core.
#
# Given a single argument, it's treated as a var in clojure.core. Two
# argument form allows a particular namespace to be referenced.
# This is generally useful because it provides access to Clojure vars
# for when you need to use a Clojure var without invoking it. Combine
# with NS.[] for this effect.
#
# Returns an instance of Familiar::NS
#
# Examples:
#
# # Require a namespace
# Familiar["clojure.set"].require
#
# # Get the union function from clojure.set
# Familiar.ns("clojure.set")[:union]
# => #'clojure.set/union
#
# Familiar.with do
# ns("clojure.set").require
# ns("clojure.set").union(hash_set(1, 2), hash_set(2, 3))
# end
# => #{1 2 3}
#
# See:
# Familiar.[]
def self.ns (ns)
# TODO cache namespaces?
Familiar::NS.new(ns || "clojure.core")
end

# Lookup a var.
#
# [ns, var] => #'ns/var
#
# [var] => #'clojure.core/var
#
# This is useful if you need to pass an existing Clojure function to a
# higher-order function.
#
# Note that there's significant overlap with Familiar.ns().
#
# Examples:
#
# # Get the inc function from core
# Familiar[:inc]
# => #'clojure.core/inc
#
# # Get the union function from clojure.set
# Familiar["clojure.set", :union]
# => #'clojure.set/union
#
# # Pass clojure.core/even? to clojure.core/filter
# Familiar.with do
# filter self[:even?], range(100)
# end
# => (0 2 4 6 ...)
#
# Familiar.with do
# require symbol("clojure.set")
# ns("clojure.set").require
# self["clojure.set", :union].invoke(hash_set(1, 2), hash_set(2, 3))
# # ... or ...
# ns("clojure.set").union(hash_set(1, 2), hash_set(2, 3))
# end
# => #{1 2 3}
#
def self.[] (ns, var = nil)
# See:
# Familiar.ns()
def self.[] (ns_name, var = nil)
if not var
var = ns
ns = "clojure.core"
var = ns_name
ns_name = "clojure.core"
end

# TODO cache namespaces?
Familiar::NS.new(ns)[var]
ns(ns_name)[var]
end

def self.method_missing(meth, *args, &block)
m = self[meth]
if m
m.invoke(*args)
else
super
end
ns(nil).send(meth, *args, &block)
end

# Make inspect and to_s look right in irb
Expand Down Expand Up @@ -178,6 +240,7 @@ def self.fn(p = nil, &code)
#############################################################################
# Seqs

# Create a lazy sequence with the code in the block.
def self.lazy_seq(&code)
Java::clojure.lang.LazySeq.new(Familiar.fn(code))
end
Expand Down Expand Up @@ -259,15 +322,13 @@ def self.future(&code)
#############################################################################
# REPL
if $0 != __FILE__
Familiar.with do
self.require symbol("clojure.repl")
end
ns("clojure.repl").require

def self.find_doc(s)
self["clojure.repl", :find_doc].invoke(s)
ns("clojure.repl").find_doc s
end

# Print docs for he given string, symbol, etc.
# Print docs for the given string, symbol, etc.
#
# Examples:
#
Expand All @@ -277,17 +338,13 @@ def self.find_doc(s)
# > Familiar.doc "clojure.repl/source"
# ... prints docs for clojure.repl/source ...
def self.doc(s)
self.with do
Familiar.eval read_string("(clojure.repl/doc #{s})")
end
self.eval read_string("(clojure.repl/doc #{s})")
end

# Same as Familiar.doc, but prints the *source code* for the given
# input.
def self.source(s)
self.with do
Familiar.eval read_string("(clojure.repl/source #{s})")
end
self.eval read_string("(clojure.repl/source #{s})")
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions test/test_familiar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class FamiliarTest < Test::Unit::TestCase
def test_can_require_a_namespace
f = Familiar
s = Familiar::NS.new("clojure.set")
assert s.require.nil?
assert_equal s, s.require
assert_equal f.hash_set(1, 2, 3), s.union(f.hash_set(1, 2), f.hash_set(3, 2))
end

Expand Down Expand Up @@ -58,7 +58,7 @@ def test_can_force_lazyseqs_with_inspect!
def test_can_get_vars_from_other_namespaces
f = Familiar
f.require f.symbol("clojure.set")
union = f["clojure.set", :union]
union = f.ns("clojure.set")[:union]
assert union
assert_equal f.hash_set(1, 2, 3), union.invoke(f.hash_set(1, 2), f.hash_set(3, 2))
end
Expand Down

0 comments on commit 3763fb3

Please sign in to comment.