Fork me on GitHub
#beginners
<
2022-04-09
>
jtth01:04:32

If I wanted to craft a very lightweight GraphQL api for a toy project that doesn’t need to talk to a DB at all, just something in memory, should I use lacinia-pedestal, or is there another solution?

Cora (she/her)02:04:12

I know you're excluding databases here but just in case you haven't seen it: hasura is amazing and easy to set up and use

practicalli-johnny09:04:23

https://github.com/juxt/grab is described as a minimal graphql library. No docs yet, but examples of use in the project unit tests I've also used Hasura (local docker image rather than service) which was just pointed to tables in postgres database and generated a Graphql API service. Hasura has an okay console for configuration, which can also be done in Yaml.

👍 1
practicalli-johnny09:04:01

https://www.apollographql.com/docs/studio/ is a very nice Graphql client (free online service, with commercial extras)

👍 1
popeye07:04:24

What is the meaning of wrapper in clojure? what wrapper do in clojure code

Martin Půda07:04:54

For example, clojure.math is wrapper over Java's Math , so you don't have to know Java or search in Oracle docs to do something. And these functions are better documented, you can require them under shorter name and are easier to use (e.g. mapping without using anonymous fn):

Martin Půda07:04:59

(require '[clojure.math :as m])
=> nil
(map m/sin [90 0 180])
=> (0.8939966636005579 0.0 -0.8011526357338304)
(map #(Math/sin %) [90 0 180])
=> (0.8939966636005579 0.0 -0.8011526357338304)
(doc m/sin)
-------------------------
clojure.math/sin
([a])
  Returns the sine of an angle.
  If a is ##NaN, ##-Inf, ##Inf => ##NaN
  If a is zero => zero with the same sign as a
  See: 
=> nil
(doc Math/sin)
=> nil

popeye07:04:14

@U01RL1YV4P7 Thank you for your response, In web application, we make use wrap-params and wrap-multiparrams similar kind of wrappers, what does it do

Martin Půda08:04:23

Oh, that's something different. I'd say that wrap- is conventional prefix for middleware name.

popeye08:04:42

can we say , takes some input and appends each request with something else

Martin Půda08:04:17

And middleware is higher-order function, which takes handler and returns a new handler with some added behaviour. See this: https://luminusweb.com/docs/middleware.html

popeye08:04:37

ahh yeah , so wrapper and midleware are same

practicalli-johnny10:04:48

A wrapper can be either 1) a Clojure project that explicitly provides functions around a library, usually in another language like Java or python (jython) 2) a middleware function that is called to process data coming into / out of a handler function (e.g. ring middleware) I guess the main different is the scope of thing being wrapped

leifericf11:04:50

In a more general and all-encompassing sense, perhaps one could say that a wrapper is an alternative interface that makes the underlying interface(s) (the “thing(s)” being wrapped) easier to use in a particular context or situation.

Sakib16:04:32

What does (vec "clojure") do?

Jon Boone16:04:02

I expect it returns a new vector containing the individual characters of the string “clojure”…

🙏 1
dpsutton17:04:14

play=> (doc vec)
-------------------------
clojure.core/vec
([coll])
  Creates a new vector containing the contents of coll. Java arrays
  will be aliased and should not be modified.
vec creates a vector when given a collection. Strings are considered collections of characters. So
play=> (vec "clojure")
[\c \l \o \j \u \r \e]
play=> (type \c)
java.lang.Character
which is a vector of the characters c, l, o, .... The repl prints characters as \c

thanks3 1
andy.fingerhut17:04:30

It is highly recommended to try such things out yourself in a local Clojure REPL to find out, by the way. A great way to get quick answers to such questions. Even if you do that, it is still certainly reasonable to ask questions like "WHY does it behave this way?"

thanks3 1
Sakib17:04:23

so \c is type of character and we can use it like any other symbol :a "a" . I'm trying to find the frequency of character in a string. So I'm doing following

(frequencies (vec "clojure-rocks"))
and find any frequency this way
(get (frequencies (vec "clojure-rocks")) \c)
Is that right?

dpsutton17:04:04

play=> (frequencies "clojure")
{\c 1, \l 1, \o 1, \j 1, \u 1, \r 1, \e 1}
you don't need to vec the string since it is already a collection. And then you can access the resulting map however you like

wow 1
dpsutton17:04:13

but the advice above was correct. play in the repl. cause errors. evaluate sub forms, etc

dpsutton17:04:06

and learn how to use the functions in clojure.repl: doc, source, apropos etc

👍 1
Sakib17:04:01

Yes, I'm doodling in REPL. Still didn't comfortable completely. This language surprising me each day.

❤️ 1
noisesmith16:04:13

> you don't need to vec the string since it is already a collection it isn't a collection, but frequencies (like most clojure collection functions) implicitly calls seq, and seq works on strings

dpsutton16:04:58

ah good clarification. it is sequable is the more correct way to put it?

noisesmith16:04:35

I think sequable is the term (see the seqable? function)

user=> (seqable? "foo")
true

Noah Moss18:04:42

is there a good reason why (class (hash-map)) => clojure.lang.PersistentArrayMap? seems like if you're calling hash-map with no args it should be an alias for clojure.lang.PersistentHashMap/EMPTY

dpsutton18:04:24

The optimization around PersistentArrayMap and PHM is purely implementation details. But reading the source, hash-map doesn't waste time counting args. It just makes a transient based on the PHM, stuffs everything in it and then gets a persistent version of it. The rule of thumb I've heard is that if you care which implementation you have between PAM and PHM you are doing it wrong.

andy.fingerhut18:04:25

I do not claim this is a good reason, but I suspect the true reason is that for most users of Clojure maps, they have no reason to care whether the class implementing it is a persistentArrayMap or a persistentHashMap

Noah Moss18:04:14

yeah, not saying that there's a good reason to care in practice. but since the hash-map function is provided to explicitly create a hash map, it seems surprising that it doesn't actually do this for the 0-args case

andy.fingerhut18:04:22

The function map? returns true for both, for example, and almost all functions on maps return equal results for the same operations on both

dpsutton18:04:38

i'm not following the surprise. it creates a hashmap and does so for the 0-args case

dpsutton18:04:51

what expectation was surprised?

dpsutton18:04:29

> Returns a new hash map with supplied mappings. seems like that promise was delivered

Noah Moss18:04:51

Well, my assumption is that a PAM is not a "hash map", even when empty. It's a map, but not a hash map. Part of my surprise is due to the first example on the https://clojuredocs.org/clojure.core/hash-map for this function making a distinction between the {} syntax for creating an "array map" and (hash-map) for creating a "hash map". But in practice they both create array maps.

Noah Moss18:04:42

Unlikely that this would ever be an issue in practice, sure. Just a bit surprising to me, since hash-map returns a PHM in every other context (even when using map literal syntax would produce a PAM). So I was curious if there was an explicit reason for this. Perhaps not.

andy.fingerhut18:04:29

Note that http://Clojuredocs.org is community written content, except for the official doc strings that are simply copied from official Clojure releases. The Clojure core team does not vet, approve, or in any way endorse the community written parts of it, unless maybe somehow they make special cases where they explicitly do (but I am not aware of any cases that they go out of their way to do so)

Noah Moss18:04:58

Understood, just mentioning it as a cause of my initial surprise about this behavior. Clearly someone else had similar assumptions here.

Cora (she/her)18:04:10

it's something about PersistentArrayMap being used for small maps and over a certain number of keys it uses PHM (iirc)

Cora (she/her)18:04:19

as an optimization

dpsutton18:04:03

Reading the source of j.u.HashMap. It also has some optimizations. https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/HashMap.java#l211 When making the buckets, if lots of things go in the bucket it will change from an array of nodes to a tree of nodes: https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/HashMap.java#l642 This type of optimization is common in these things. The docs of this class and https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/LinkedHashMap.java point out that small hash maps are extremely common. Clojure takes advantage of this and the equality semantics of Clojure to provide an even better optimization: the array map. Because of Clojure's equality semantics, we don't even have to explicitly hash things, we can just keep a 16 wide array and traverse through it because this is faster to walk that and check a notion of equality than to hash, consider collisions, etc. But it is still a "hash map", just a tuned and optimized one

andy.fingerhut20:04:47

@U11BV7MTK Well, I would say that a PersistentArrayMap is still a Clojure map, but it is clearly not a PersistentHashMap, which is sometimes what a Clojurist means when they say hash map.

andy.fingerhut20:04:14

As mentioned above, for almost all use cases, PersistentHashMap and PersistentArrayMap are indistinguishable from their behavior.

dpsutton20:04:05

Yeah fair. I'm just thinking there's a colloquial use of “hashmap” to mean map

Noah Moss20:04:21

Yeah, that's the crux of my initial question. Since there are distinct hash-map and array-map functions which specifically return instances of PersistentHashMap and PersistentArrayMap for all non-empty cases, it seemed odd that this wouldn't be true for the empty case as well. If the answer is just "the implementation shouldn't matter to the end user" then why expose distinct array-map and hash-map functions in the first place?

Noah Moss20:04:46

What I've landed on is that array maps have additional semantics over hash maps: they preserve the input order of elements. So this warrants having array-map as a distinct function for constructing them, for maps > 10 elements. But you don't get any additional semantics by using a hash map where you'd otherwise have an array map. So there's no downside to the end user for having (hash-map) return an empty array map, despite the function name. Anyway, none of this has a motivating problem, just curiosity, so not worth digging into too deeply.

mister_m18:04:40

I'm learning a little core.async and I made a small wrapper around the hato callback API's get to return a core.async channel. Does this seem problematic at all? The simplest version of this would be just to create a channel, and have the success/failure callbacks both put! asynchronously to the same channel and then return that channel. Does adding a go block here for some instrumentation make this behave in any kind of unexpected way? Does this look fine?

Konrad Claesson20:04:07

I have implemented a function that takes a sequence of maps and prints it as tab-separated values:

(defn print-tsv
  ([ks rows]
   (when (seq rows)
     (println)
     (println (str/join "\t" ks))
     (doseq [row rows]
       (println (str/join "\t" row)))))
  ([rows] (print-tsv (keys (first rows)) (map vals rows))))
Input
(print-tsv '({:log "some text", :id 123} {:log "some other text" :id 124}))
Output
:log	:id
some text	123
some other text	124
How could I rewrite my function to return the output as a string rather than print it?

djblue20:04:34

Quickest way would be to use with-out-str

Martin Půda20:04:11

(defn print-tsv
  ([ks rows] (->> (map #(str/join "\t" %) rows)
                  (str/join "\n")
                  (str (str/join "\t" ks) "\n")))
  ([rows] (print-tsv (keys (first rows))
                     (map vals rows))))

(print-tsv '({:log "some text", :id 123} {:log "some other text" :id 124}))
=> ":log\t:id\nsome text\t123\nsome other text\t124"

Martin Půda20:04:25

By the way, you can use print-table: (with-out-str (clojure.pprint/print-table [{:log "some text", :id 123} {:log "some other text" :id 124}]))

Konrad Claesson20:04:34

Thanks to both of you. Yeah, I'm aware of clojure.pprint/print-table, but for my use-case I want a tsv rather than a pretty table.

nate22:04:43

one way of doing it:

(defn as-tsv
  ([ks rows]
   (when (seq rows)
     (cons (str/join "\t" ks)
           (for [row rows]
             (str/join "\t" (map row ks))))))
  ([rows]
   (as-tsv (keys (first rows)) rows)))

(run! println (as-tsv '({:log "some text", :id 123}
                        {:log "some other text" :id 124})))
I prefer making a sequence of lines to making one whole string so that I don't force all the data into memory at once as a big string. Then it's simple to print them out with (run! println ...). Also, a potential issue with your first impl is the (map vals rows) in the single arity call. That assumes that the values are all in the same order for all maps, which is only true if they have 8 or fewer keys. After that they're not in order.

👍 1
Konrad Claesson22:04:22

I am reading a tsv using the below code

(-> "/Users/konrad/tmp/input.tsv"
    io/file
    io/reader
    (csv/read-csv :separator \tab)
    (csv-data->maps)
    first)
and get the output
{::a "1",
 ::b "2",
 ::d "3"}
If I evaluate
(-> "/Users/konrad/tmp/input.tsv"
    io/file
    io/reader
    (csv/read-csv :separator \tab)
    (csv-data->maps)
    first
    ::a)
the output becomes nil. However, if I evaluate
(-> {::a "1",
     ::b "2",
     ::d "3"}
    ::a)
I get
"1"
Why do I get the output nil when I use ::a in the second snippet?

dpsutton23:04:13

can you post the source of csv-data->maps?

dpsutton23:04:09

the answer is most likely in understanding what ::a means. keywords can have a namespace. using an autoresolved keyword like ::a means use the current namespace. Are you running this from a repl or are you running it from the command line?

Konrad Claesson23:04:06

The source of csv-data->maps is

(defn csv-data->maps [csv-data]
  (map zipmap
       (->> (first csv-data)
            (map keyword)
            repeat)
       (rest csv-data)))
I am running the code in a cider repl in emacs

dpsutton23:04:04

i suspect you are making funny keys then. drop the map keyword bit and see what the output is for the first row of the csv

dpsutton23:04:54

play=> {(keyword ":a") 1}
{::a 1}
play=> (get {(keyword ":a") 1} ::a)
nil

Konrad Claesson23:04:45

If I comment out (map keyword) from csv-data->maps and run

(->>
    (-> "/Users/konrad/tmp/input.tsv"
        io/file
        io/reader
        (csv/read-csv :separator \tab)
        (csv-data->maps)
        (first)
        (get ":a"))
I get the expected output
"1"

dpsutton23:04:00

yeah. so you are making bad keywords

dpsutton23:04:28

the function keyword will take a string and return a keyword. But it won't validate it. you want those columns to be named "a" not ":a"

dpsutton23:04:56

play=> (= :a (keyword "a"))
true
play=> (= ::a (keyword ":a"))
false
play=> ::a
:play/a
play=> 

dpsutton23:04:35

another issue is you can make keywords that are not readable: evaluate something like (keyword "this can't possibly work when typing in the repl")