This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-01-16
Channels
- # aleph (2)
- # announcements (1)
- # aws (2)
- # babashka (5)
- # beginners (122)
- # boot-dev (1)
- # cider (3)
- # clara (7)
- # cljdoc (11)
- # clojure (161)
- # clojure-dev (45)
- # clojure-europe (8)
- # clojure-france (1)
- # clojure-india (1)
- # clojure-italy (3)
- # clojure-nl (11)
- # clojure-uk (34)
- # clojurebridge (2)
- # clojurescript (13)
- # cryogen (10)
- # cursive (13)
- # datomic (25)
- # emacs (8)
- # fulcro (76)
- # graalvm (2)
- # jackdaw (5)
- # jobs-discuss (2)
- # juxt (13)
- # off-topic (13)
- # pathom (5)
- # pedestal (7)
- # quil (2)
- # reitit (9)
- # remote-jobs (4)
- # schema (1)
- # shadow-cljs (33)
- # spacemacs (8)
- # sql (9)
- # vim (2)
- # vrac (2)
Hello, I am just getting started with prismatic/schema and having some issues. It would be great if anyone could help me understand these problems I'm having! π:skin-tone-2:
https://www.reddit.com/r/Clojure/comments/epcw3j/possible_to_use_prismaticschema_to_describe_let/
@derpocious Looking at the documentation for Schema, there seems to be a version of letfn
but not let
so I think that answers your first question (nope, not possible). No idea about your second question -- been far too long since I last used Schema to offer much help there, sorry (I was switching back and forth between core.typed and Schema for a while, then gave up on both of them, and then started using Spec instead since it's built into Clojure).
I seem to recall that core.typed lets you annotate let
? (but it's static typing rather than Schema's runtime type checking assertions)
Ah, my bad, it allows annotations on forms which you can use in let
but it doesn't share the normal :-
type syntax.
Thanks @seancorfield! Wait so scheme does allow you to annotate let
? Yes I tried the normal :-
syntax and it tried to just use -
as the value. haha
Also, just out of curiosity would the code look similar using spec or is that is that all in a completely different file from the source code?
No, I said Schema does not let you annotate let
-- but it does have a version of letfn
that lets you annotate local function definitions.
Spec is similar in some ways to Schema but very different in other ways. But it's built into Clojure (since 1.9).
What I disliked about both core.typed and Schema were the syntax annotations, especially with the latter since it gives the mistaken impression that it is somehow adding "types" to Clojure (which it isn't: it just adds more assertions at runtime).
core.typed at least is gradual typing and you run a static type checking phase separate from running/testing your code. The problems with core.typed are different (it's a research project and not really suitable for idiomatic production Clojure code in the large, in my opinion).
If you're "getting started" with Clojure, I'd recommend learning Spec -- since it is "standard" (built-in) -- rather than learning libraries that change the syntax.
I'm curious as to how you started down the path of looking at Schema, rather than Spec @derpocious?
Thanks sean. I started with schema because I like TypeScript and wanted something that I can put right next to my Clojure functions so that humans can read what is being passed around and some automated thing is enforcing it. That seemed to be exactly what schema was about and not what spec was about.
Personally, having tried to use both core.typed and Schema at different times on our production code base (multiple times, in both cases), I think you'd be better off embracing the idiomatic Clojure way of just not trying to use "types" -- embrace the dynamic nature and the abstractions.
I think Spec is very valuable -- for describing data structures, separate from function signatures, and for generative testing, and I think it exemplifies the "lessons learned" from other, earlier approaches to "type checking" in Clojure (and, yeah, I trust Rich knows what he's doing).
Schema is only as "good" as your tests, since all it does is sprinkle assertions all over your code (using non-standard syntax).
core.typed is closer to TypeScript in terms of actually providing "gradual typing" but it's very rough at the boundary between Typed Clojure and untyped Clojure (a known problem and a very interesting one from an academic point of view that Ambrose is working on as a research project, as are the folks in the Typed Racket community).
I see, but I just want to know what the shape of a joke is: https://github.com/JimLynchCodes/Programming-Jokes-Generator/blob/master/src/main/clojurescript/serverless/functions.cljs#L41
can't spec just generate literally any map with a :body key? how does it know to generate the right data shape?
core.typed uses a similar annotation syntax to Schema (mostly) but it is sometimes extremely hard to annotate some idiomatic Clojure in a way that satisfies that type checker (and you end up refactoring code to non-idiomatic Clojure).
Really, I just want to tell the next programmer what the shape of a joke is.
Spec can indeed specify the "shape" of a data structure. That's exactly what it's for. But it is explicit and doesn't provide additional syntax: so you can add assertions explicitly that check data is "valid".
hmm what to you mean by it being explicit?
You would literally add code that performed the valid?
check (or assert
).
do you have any sample repos of a simple project where this is used?
I'd recommend you start with the official guide to Clojure Spec https://clojure.org/guides/spec
If you want to see what Spec looks like in production code, in an open source project: https://github.com/seancorfield/next-jdbc/blob/master/src/next/jdbc/specs.clj
(there are only minimal data specs in that file, they're mostly function signature specs)
are they checking the shape of the thing that is returned from the function?
We use Spec very heavily at work -- in production code as well as in dev/test -- but that's all closed source I'm afraid. I did blog about how we use Spec tho' https://corfield.org/blog/2019/09/13/using-spec/
does anyone put the specs right inline with the source code functions?
Yes, but in next.jdbc
they are optional (for instrumentation) so they're in a separate ns. I talk about that in my blog post.
In Spec, return type checking is part of generative testing, not instrumentation (which is argument checking). They are (and should be) two separate approaches. Instrumentation (argument checking) is about making sure callers pass the right things in. Generative testing (return type checking, and relationship to arguments) is about making sure the function itself behaves correctly -- and the generative testing engine makes lots of calls to your function with conforming but randomly generated arguments.
(you can't use generative testing with a library like next.jdbc
because it is inherently side-effecting and many of its arguments and return values are complex, mutable Java objects)
hmmm interesting
It is late here and I need to go, but I will think hard about this
thank you. π
NP. Happy to answer questions about Spec (or Clojure in general) any time. I'm US West Coast but I'm online a lot π
great, thanks. I'm sure I will have more questions soon!
You can use assoc-in
and/or update-in
. Works with any nested mix of vectors and maps
HI there friends. I am messing around with ring but I am stuck trying to parse the request body to a clojure datastructure. My question is extremely simmilar to this one: https://stackoverflow.com/questions/42111795/parse-json-body-from-http-request-with-ring-and-re-frame-http-fx but I am not using either figwheel nor templates. I am posting the following to my server
curl --data '{"perico":"palotes"}' --request POST ''
And as far as I get it from the ring-json documentation, the following code should parse my input data to a clojure data stracture.
(defn deal-with-params [params]
(pp/pprint params))
(defn my-handler [request]
(do
(println (req/body-string request))
(println (:body request))
(deal-with-params (:body request))
(response/response {:calculado 190})))
(def app (-> my-handler
(wrap-json-body {:keywords true})
wrap-json-response))
The output I get in the server is
{"perico":"palotes"}
#object[org.eclipse.jetty.server.HttpInputOverHTTP 0x593853c2 HttpInputOverHTTP@593853c2]
#object[org.eclipse.jetty.server.HttpInputOverHTTP 0x593853c2 "HttpInputOverHTTP@593853c2"]
So body-string works as expected, but the body is not parsed. The response I get to my post seems to be a valid json.
My project ring configuration is
:plugins [[lein-ring "0.12.5"]]
:ring {:handler avm-big-query.core/app
:auto-reload? true}
And my ns declaration is
(ns my-ring.core
(:require [ :as io]
[clojure.string :as s]
[clojure.pprint :as pp]
[clojure.reflect :as reflect]
[ring.middleware.json :refer
[wrap-json-response wrap-json-params wrap-json-body]]
[ring.util.response :as response]
[ring.util.request :as req]
[ring.adapter.jetty :as jetty])
Any pointers on what am I doing wrong? Thanks a lot!I would guess that your body is an input stream, and it's getting consumed by your first println. You should be able to use a let binding to keep that initial value as a string and then work with it from there.
Thanks a lot for your reply. That was my first guess. So I only printed it once, same results. If I change my handler to
(defn my-handler [request]
(let [params (:body request)]
(do
(pp/pprint params)
;; (println (:body request))
;; (deal-with-params params)
(response/response {:calculado 190}))))
I get the following output
#object[org.eclipse.jetty.server.HttpInputOverHTTP 0x56dd6268 "HttpInputOverHTTP@56dd6268"]
Furthermore, checking the ring-clojure source code here: https://github.com/ring-clojure/ring-json/blob/master/src/ring/middleware/json.clj
The body field in the request should be replaced by a clojure map, as such, I do not believe that reprinting should cause an issue. But I am not so sure about thatThat middleware may not be getting activated because your curl doesn't have an application/json
content type.
@U0DUNNKT2 you are right, thanks a lot!
Sure thing!
Looking at spec-tools lib i can see that by default they transform json number to double or float. Is this a good default? I would expect numbers to be represented as a BigDecimal. am I missing something?
(defn string->double [_ x]
(if (string? x)
(try
#?(:clj (Double/parseDouble x)
:cljs (let [x' (js/parseFloat x)]
(if (js/isNaN x') x x')))
(catch #?(:clj Exception, :cljs js/Error) _ x))
x))
Forget it. The change was added yesterday into the master. https://github.com/metosin/spec-tools/commit/c391ae7bb7a2b517f7ba197b6cea326439706896#diff-b81e4c0c67e17c4fc6f11fdfc45be3cd
I'm curious if there are any style opinions of doing something like (if (first xs) foo bar)
vs (if (empty? xs) bar foo)
Is either approach idiomatic or are there some pitfalls to one that I'm not seeing? I just encountered that first case and thought it was nifty as I always go with the empty?
approach instead.
(if (seq xs) foo bar)
is used a lot in clojure.core
(first xs)
will realize first item in lazy seq, sometimes this is not desired behaviour
I'm always in low key fear that I'm not utilizing or understanding laziness behavior and when it's going to happen unfortunately
Another potential issue with (if (first xs) ...)
to decide whether there are more elements or not, is if there is another element, and the first one is nil
or false
So I just tried (seq [nil]) and it gives me (nil)
. Is that going to give me the expected behavior I want though?
or more precisely, I guess, a sequence of one element.
ahh, of course, that makes sense. I was just thinking out loud too quickly on that one.
@U04V4KLKC
> (first xs)
will realize first item in lazy seq, sometimes this is not desired behaviour
any method of checking emptiness will do this, seq
and empty?
included
(ins)user=> (def ls (lazy-seq (cons 1 (println "realized"))))
#'user/ls
(cmd)user=> (empty? ls)
realized
false
(example relies on the fact that lazy-seq and cons pun nil / empty list)(rest coll)
Consider using a list
subvec
can be used to get a sub-vector of a vector in O(1) time
if you want a collection type that is cheap to insert and also cheap to remove, and does each at opposite ends, you can use clojure.lang.PersistentQueue/EMPTY
there are good cases for using strings - eg. if the keys come from a text source, that can include things that technically shouldn't be in keywords
Actually this eis my case and i just donβt want to convert them into keywords, look more steps to me. So wanted to know if i can just avoid transformation into keyword
clojure.core/keyword will happily turn all kinds of nonsense into keywords, the real question is if that's actually helping you
(ins)user=> (keyword "")
:
(ins)user=> (keyword "foo
bar
baz")
:foo
bar
baz
(ins)user=> (keyword " what ")
: what
Very clear your point @U051SS2EU, this exactly the kind of stuffs I am going to deal with
@quieterkali no slower than using an arbitrary object as a key. Keywords have an optimization in that they're interned into the runtime. Only a single instance of a particular keyword exists at any time, so you save memory
Keywords also cache their hashcode, whereas an arbitrary object might not cache the hashcode
@ghadi might also add that keywords allow one to look up in map via the (keyword map) syntax, which is one thing I found very handy in clojure
That is unique to keywords in Clojure. You can use (my-map any-type-of-key)
for arbitrary types of keys, though.
it works with symbols too! - though I have yet to see anyone exploit the feature in the wild
@U0CMVHBL2 I didn't notice that earlier
I am occasionally reminded symbols can be looked up in a map that way, too, then completely forget about it until occasionally reminded again later π
yes, while we're at it, someone yesterday mentioned that we can use (set element) to check if set contains member
Yes, which works well as long as you do not try to use it to check for existence of nil
or false
. Those might be impossible in many use cases for sets, but if you do allow them, or are writing code intended to work for arbitrary sets that might contain those values, contains?
is safer.
I just tried things it (set member) seems to return member if it is in set, otherwise nil
so if-ing against it will evaluate to falsey for false and nil even if false or nil is in set
Interesting, there is any reason why the :opt-un option below is not working?
(def numero-casa (s/def ::numero-casa int?))
(s/keys :req-un [numero-casa])
(s/keys :opt-un [numero-casa])
It produces:
Caused by java.lang.AssertionError
Assert failed: all keys must be namespace-qualified
keywords (every? (fn* [p1__1917#] (c/and (keyword?
p1__1917#) (namespace p1__1917#))) (concat req-keys
req-un-specs opt opt-un))
I don't know spec well enough to say, but perhaps hiredman was using "it" in "it has a bug" to refer to your use of :req-un in your example code above?
Is what a known problem?
this behavior with clojure.spec
:
(def new-test (s/def ::new-spec int?))
(s/valid? (s/keys :req-un [new-test]) {:new-spec 10})
;; => true
(s/keys :opt-un [new-test]) => java.lang.AssertionError
the first two lines both do things that are not supported, so the problem here is your code
well, the last line too
ok, well, not going to fix that
spec 2 is entirely different in this area
I had this idea to include metadata in the keyword and manage to do so this way [i know is not supported] just coding around
Keywords are shared, so metadata on them would be a bad idea
clojure keywords are interned. the result is that anytime you have a keyword like :foo
, then every :foo
is not only the same value, but the exact same object. so if you put meta data on a keyword, :foo
, then every other keyword named βfooβ will also have the same metadata
here are some references that provide a little more detail:
actual keyword source: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Keyword.java#L32
Interning is more common for strings, but itβs the same idea clojure uses for keywords:
https://en.wikipedia.org/wiki/String_interning
https://stackoverflow.com/questions/1136826/what-does-python-sys-intern-do-and-when-should-it-be-used
"definition" as in original source? no, it's gone. however if it was defined in a file the metadata on the var will include the filename and line no.
If you mean the data value like (fn [x] (inc x))
that the Clojure compiler compiled to JVM byte code, then no, unless someone has created, and you are using, some special system that tries to keep those values around, they are not preserved by Clojure
neither with the function object/value, nor from any var whose value is that function.
vars do have the metadata that @U050MP39D mentions, that's what clojure.repl/source uses for example (as long as you used require
rather than load-file or direct repl input to define the var)
