Fork me on GitHub
#beginners
<
2020-01-16
>
derpocious02:01:59

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:

seancorfield03:01:53

@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).

seancorfield03:01:43

I seem to recall that core.typed lets you annotate let? (but it's static typing rather than Schema's runtime type checking assertions)

seancorfield03:01:29

Ah, my bad, it allows annotations on forms which you can use in let but it doesn't share the normal :- type syntax.

derpocious04:01:34

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

derpocious04:01:41

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?

seancorfield04:01:57

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.

seancorfield04:01:03

Spec is similar in some ways to Schema but very different in other ways. But it's built into Clojure (since 1.9).

seancorfield04:01:10

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).

seancorfield04:01:29

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).

seancorfield04:01:56

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.

seancorfield04:01:18

I'm curious as to how you started down the path of looking at Schema, rather than Spec @derpocious?

derpocious04:01:26

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.

seancorfield04:01:46

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.

seancorfield04:01:20

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).

seancorfield04:01:01

Schema is only as "good" as your tests, since all it does is sprinkle assertions all over your code (using non-standard syntax).

seancorfield04:01:18

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).

derpocious04:01:38

can't spec just generate literally any map with a :body key? how does it know to generate the right data shape?

seancorfield04:01:06

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).

derpocious04:01:09

Really, I just want to tell the next programmer what the shape of a joke is.

seancorfield04:01:14

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".

derpocious04:01:00

hmm what to you mean by it being explicit?

seancorfield04:01:35

You would literally add code that performed the valid? check (or assert).

derpocious04:01:39

do you have any sample repos of a simple project where this is used?

seancorfield04:01:10

I'd recommend you start with the official guide to Clojure Spec https://clojure.org/guides/spec

seancorfield04:01:19

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

seancorfield04:01:07

(there are only minimal data specs in that file, they're mostly function signature specs)

derpocious04:01:18

are they checking the shape of the thing that is returned from the function?

seancorfield04:01:29

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/

derpocious04:01:39

does anyone put the specs right inline with the source code functions?

seancorfield04:01:11

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.

seancorfield05:01:36

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.

seancorfield05:01:44

(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)

derpocious05:01:43

hmmm interesting

derpocious05:01:03

It is late here and I need to go, but I will think hard about this

derpocious05:01:10

thank you. πŸ™

seancorfield05:01:43

NP. Happy to answer questions about Spec (or Clojure in general) any time. I'm US West Coast but I'm online a lot πŸ™‚

derpocious05:01:27

great, thanks. I'm sure I will have more questions soon!

feikas11:01:07

Hello! how to update into nested map? (map inside map)

jaihindhreddy11:01:05

You can use assoc-in and/or update-in. Works with any nested mix of vectors and maps

joaquin.iglesias12:01:41

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 [email protected]]
#object[org.eclipse.jetty.server.HttpInputOverHTTP 0x593853c2 "[email protected]"]
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!

codonnell12:01:43

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.

joaquin.iglesias12:01:44

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 "[email protected]"]
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 that

codonnell12:01:28

That middleware may not be getting activated because your curl doesn't have an application/json content type.

joaquin.iglesias13:01:26

@ you are right, thanks a lot!

zara13:01:07

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))

chase-lambert16:01:55

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.

delaguardo16:01:07

(if (seq xs) foo bar) is used a lot in clojure.core

delaguardo16:01:25

(first xs) will realize first item in lazy seq, sometimes this is not desired behaviour

chase-lambert16:01:46

ahh ok, I think I remember reading that about the (seq xs) a while back too.

chase-lambert16:01:17

I'm always in low key fear that I'm not utilizing or understanding laziness behavior and when it's going to happen unfortunately

andy.fingerhut17:01:31

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

chase-lambert17:01:16

Ahh, that is a good point.

chase-lambert17:01:32

So I just tried (seq [nil]) and it gives me (nil) . Is that going to give me the expected behavior I want though?

andy.fingerhut17:01:42

or more precisely, I guess, a sequence of one element.

chase-lambert17:01:00

ahh, of course, that makes sense. I was just thinking out loud too quickly on that one.

noisesmith18:01:52

@ > (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

noisesmith18:01:12

(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)

feikas18:01:06

hi, how to pop into 0th possition of array

lucascvassalli18:01:47

Consider using a list

andy.fingerhut18:01:15

subvec can be used to get a sub-vector of a vector in O(1) time

noisesmith18:01:37

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

quieterkali19:01:48

Hey folks, is there any perfomance issue on using numbers or string as map keys?

nberani19:01:58

no, there is no performance issue when using strings or numbers for map keys

nberani19:01:24

but what I saw most frequently in clojure for map keys are :keywords

noisesmith19:01:52

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

quieterkali19:01:16

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

noisesmith19:01:57

that's what I'd recommend yes

noisesmith19:01:16

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   

quieterkali19:01:15

Very clear your point @, this exactly the kind of stuffs I am going to deal with

dpsutton19:01:43

haven't heard of it. are you seeing any issues?

ghadi19:01:26

@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

ghadi19:01:59

Keywords also cache their hashcode, whereas an arbitrary object might not cache the hashcode

quieterkali19:01:05

@ghadi thank you for this valuable info. I will keep that in mind thanks3

nberani19:01:58

@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

andy.fingerhut19:01:28

That is unique to keywords in Clojure. You can use (my-map any-type-of-key) for arbitrary types of keys, though.

noisesmith19:01:51

it works with symbols too! - though I have yet to see anyone exploit the feature in the wild

noisesmith19:01:19

usually it comes up when people ask why ('or true :wat) returns :wat

nberani19:01:25

@ I didn't notice that earlier

andy.fingerhut19:01:21

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 πŸ™‚

nberani19:01:56

yes, while we're at it, someone yesterday mentioned that we can use (set element) to check if set contains member

andy.fingerhut19:01:51

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.

nberani19:01:33

:+1: thanks

nberani19:01:56

I just tried things it (set member) seems to return member if it is in set, otherwise nil

nberani19:01:44

so if-ing against it will evaluate to falsey for false and nil even if false or nil is in set

iagwanderson19:01:36

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))

ghadi19:01:27

error message seems pretty clear

ghadi19:01:41

you are giving it a symbol numero-casa but it wants a ns-qualified keyword

ghadi19:01:17

(s/def ::numero-casa int?)
(s/def ::my-map-thing (s/keys :req-un [::numero-casa]))

ghadi19:01:50

no need to have a top level def, just s/def

iagwanderson19:01:23

yes, but why the :req-un option works fine?

hiredman19:01:58

Because it has a bug

hiredman19:01:14

It likely doesn't work fine when you go to use the spec

iagwanderson21:01:08

is this a known problem? there's an open ticket?

andy.fingerhut21:01:47

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?

alexmiller21:01:34

Is what a known problem?

iagwanderson21:01:53

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

alexmiller21:01:55

the first two lines both do things that are not supported, so the problem here is your code

alexmiller21:01:24

well, the last line too

iagwanderson21:01:45

yes, but I was expecting to get the exception in both cases.

alexmiller21:01:07

ok, well, not going to fix that

alexmiller21:01:47

spec 2 is entirely different in this area

iagwanderson21:01:53

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

alexmiller23:01:59

Keywords are shared, so metadata on them would be a bad idea

iagwanderson00:01:01

I'm sorry but, could you explain more about this? I didn't understand your point.

smith.adriane01:01:24

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

iagwanderson01:01:28

@ thank you for the explanation and references!

vachichng23:01:15

is it possible to get the definition of a function from a var to that function?

bfabry23:01:05

"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.

andy.fingerhut23:01:06

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

andy.fingerhut23:01:22

neither with the function object/value, nor from any var whose value is that function.

noisesmith23:01:33

vars do have the metadata that @ 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)

vachichng23:01:05

thank you all!