plumatic / schema

Clojure(Script) library for declarative data description and validation
Other
2.4k stars 256 forks source link

let destructuring in lexical closure #350

Closed vitalyper closed 8 years ago

vitalyper commented 8 years ago

Is there a way to make this work or "let destructuring" is not supported?

user> (require '[schema.core :as s])
nil
user> (s/defn foo []
        (fn [op & args]
          (condp = op
            :t (let [[i :- s/Int] args]
                 (* i 2)))))

java.lang.Exception: Unsupported binding form: :-

w01fe commented 8 years ago

Can you please clarify what you mean by "lexical closure" in this context? There is not currently a s/let macro, if that's what you mean. It's something we've thought about, but I don't think it's possible to support with the same performance that the existing schema macros provide. I'm happy to elaborate more if you have questions, but if you can describe your use case in more detail and what you're hoping to get from Schema it would be very useful in providing a helpful response. Thanks!

vitalyper commented 8 years ago

Thanks for quick reply.

There is not currently a s/let macro, if that's what you mean.

Answers the question.

foo returns a function (lexical closure over empty args in this case) which could be called like

(foo-fn :t 11)

where in let I want to destruct and assert that s/Int was passed in.

It's something we've thought about, but I don't think it's possible to support with the same performance that the existing schema macros provide.

It is sad, as it limits Schema to function args use case only. FWIK, in Scheme at least, let is sugar on top of anonymous functions (lambda in Scheme). So I would hope that s/let could piggyback on s/defn.

w01fe commented 8 years ago

No problem, thanks for the details.

I guess my question is, why do you want to schematize the args? Do you want to check the schemas at test time? Or just for documentation purposes?

The issue is that creating the schema checker is somewhat expensive, and unlike s/defn or s/fn (which are typically created once and called many times), there is nowhere to 'hoist' this creation so that it can be done once and then used repeatedly. (Instead, the checker would have to be created each time). That could be acceptable for some use cases, but it's something we would have to think about more.

Of course, you can still do schema checking inside a function by manually using s/validate or s/validator (which can be used to manually hoist the creation outside).

vitalyper commented 8 years ago

My use case is to use Schema coercion and validation when receiving raw data from json or rest. As below shows there is a workaround - use map destructuring in let.

user> (s/def UserInfo
        {:name s/Str
         :id Long})
#'user/UserInfo
user> (defn new-user-mgr []
        ;; Keep state
        ;; (let [users (ref {})])
        (fn [op & args]
          (condp = op
            :list (comment (@users))
            ;; Want to destruct UserInfo as a whole
            ;; :add (let [[user-info :- UserInfo] (nth args 0)]
            ;; As a workaround we could destruct as map
            :add (let [{name :name id :id} (nth args 0)]
                   ;; add user to users
                   (println "name" name "id" id)))))
#'user/new-user-mgr
user> ((new-user-mgr) :add {:name "john" :id 11})
name john id 11
nil
user> 

So, after some more thinking schema destructuring in let doesn't buy me that much - UserInfo is just Schema verified and coerced clojure map.

Please feel free to close this.

w01fe commented 8 years ago

OK, thanks. You might also look into letk in our plumbing lib -- it does safe map destructuring.,