fl00r
fl00r

Reputation: 83680

apply macros to a vector

Imagine having a dynamically generated specs for cat

[:first-name string? :surname string?]

Now I want to use them with cljs.spec.alpha/cat.

In plain Clojure I can write a macro that will cons a macro with args and eval it, right? But for ClojureScript it is not as easy because macros are compile-time beasts and also eval is also kinda different thing.

What are workarounds to apply my vector of args to a macro in ClojureScript?

Upvotes: 2

Views: 160

Answers (2)

Mike Fikes
Mike Fikes

Reputation: 3527

Is your spec generated symbolically at compile time? If so, perhaps you could use a macro as you had suggested.

(ns foo.core)

(defmacro gen-spec []
  `'~(into '[:first-name string?] '[:surname string?]))

(defmacro apply-macro [name args-form]
  `(~name ~@(eval args-form)))

Here is an example using it

$ clj -m cljs.main
ClojureScript 1.10.339
cljs.user=> (require '[clojure.spec.alpha :as s])
nil
cljs.user=> (require-macros 'foo.core)
nil
cljs.user=> (foo.core/gen-spec)
[:first-name string? :surname string?]
cljs.user=> (foo.core/apply-macro s/cat (foo.core/gen-spec))
{:cljs.spec.alpha/op :cljs.spec.alpha/pcat, :ps [#object[cljs$core$string_QMARK_] #object[cljs$core$string_QMARK_]], :ret {}, :ks [:first-name :surname], :forms [cljs.core/string? cljs.core/string?], :rep+ nil}

Note how gen-spec builds up the spec symbolically and returns it in quoted form.

Upvotes: 2

Taylor Wood
Taylor Wood

Reputation: 16194

Unfortunately this isn't easy for dynamic/data-driven specs, but that's apparently being worked on for a future release.

I think the only workarounds are to eval, or use cljs.spec.alpha internal implementation as you mentioned.

cljs.user=> (def cat-args [:x string?])
#'cljs.user/cat-args
cljs.user=> (s/def ::foo (eval (cons 's/cat cat-args)))
:cljs.user/foo
cljs.user=> (s/conform ::foo '("foo"))
{:x "foo"}

Upvotes: 2

Related Questions