Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure Metaprogramming Question (for a beginner!)

Tags:

clojure

All, I'm starting to take a look at the Clojure language, and had a couple questions about something I'm trying to do. The broad objective is to alias the sequence function every? to all?. I'm sure there's a function or macro that does alias-ing (or something along those lines) but I wanted to see if it was possible with some of the basic constructs I know thus far. My approach was going to be to define a function called all? that applies its arguments to the every? implementation.

I'm curious to see if this can be made agnostic, so I wanted to parameter my alias function to take two arguments, the new name (as a Keyword) and the old name (as a function reference). In striving towards this goal, I've encountered two problems.

1) Defining named functions with Keywords throws errors. Apparently it wants clojure.lang.IObj.

user=> (defn :foo "bar")     
java.lang.ClassCastException: clojure.lang.Keyword cannot be cast to clojure.lang.IObj (NO_SOURCE_FILE:0)

Is there a function to cast a Keyword to an IObj, or other means to parameterize the name of a newly defined function with some provided value? (In Ruby, define_method amongst other techniques does this)

irb(main)> self.class.instance_eval do
irb(main)* define_method(:foo) { "bar" }
irb(main)> end
=> #<Proc>
irb(main)> foo
=> "bar"

2) Collect all arguments to a function into a single variable. Even basic functions such as (+ 1 2 3 4) take a variable amount of arguments. All the function definition techniques I've seen so far take a specific amount of arguments, with no way to just aggregate everything in a list for handling in the function body. Once again, what I'm going for is done in Ruby like so:

irb(main)> def foo(*args)
irb(main)> p args
irb(main)> end
=> nil
irb(main)> foo(1, 2, 3)
[1, 2, 3]
=> nil

Thanks for any help you can provide me!

like image 801
Evan Senter Avatar asked Jul 06 '10 03:07

Evan Senter


2 Answers

I'll answer in bullet points, since the questions can be split neatly into a number of separate issues.

  • Something which is implicitly contained in what is to follow, but which perhaps warrants a bullet of its own: the top-level objects created by def & Co. (and in particular by defn) are Vars. So what you actually want to do is to alias a Var; functions are just regular values which don't really have names (except they may have a name bound to themselves locally inside their bodies; that's nothing to do with the issue at hand, though).

  • There is indeed an "aliasing macro" available in Clojure -- clojure.contrib.def/defalias:

    (use '[clojure.contrib.def :only [defalias]])
    (defalias foo bar)
    ; => foo can now be used in place of bar
    

    The advantage of this over (def foo bar) is that it copies over metadata (such as the docstring); it even appears to work with macros in the current HEAD, although I recall a bug which prevented that in earlier versions.

  • Vars are named by symbols, not keywords. Symbol literals in Clojure (and other Lisps) do not start with colons (:foo is a keyword, not a symbol). Thus to define a function called foo you should write

    (defn foo [...] ...)
    
  • defn is a helper macro easing the creation of new function-holding Vars by allowing the programmer to use a mix of def & fn syntax. So defn is out of question for creating Vars with preexisting values (which might be functions), as is required for creating aliases; use defalias or simply def instead.

  • To create a variadic function, use the following syntax:

    (fn [x y & args] ...)
    

    x and y will be required positional arguments; the rest of the arguments passed to the function (any number of them) will be collected into a seq and available under the name args. You don't have to specify any "required positional arguments" if they are not needed: (fn [& args] ...).

    To create a Var holding a variadic function, use

    (defn foo [x y & args] ...)
    
  • To apply a function to some arguments you've got assembled into a seqable object (such as the args seq in the above examples or perhaps a vector &c.), use apply:

    (defn all? [& args]
      (apply every? args))
    
  • If you want to write a function to create aliases -- as opposed to a macro -- you'll need to investigate the functions intern, with-meta, meta -- and possibly resolve / ns-resolve, depending on whether the function is to accept symbols or Vars. I'll leave filling in the details as an exercise to the reader. :-)

like image 124
Michał Marczyk Avatar answered Oct 26 '22 09:10

Michał Marczyk


All you need to do is bind the every? function to the all? symbol, which is done via def:

(def all? every?)

For a bit more on this, see Clojure macro to create a synonym for a function

like image 28
G__ Avatar answered Oct 26 '22 09:10

G__