Fork me on GitHub
#clojure
<
2021-11-03
>
lilactown00:11:44

what do people think about one-function libraries?

lilactown00:11:04

it seems like it might be better to publish a blog post

phronmophobic00:11:07

depends on the function, I guess

šŸ’Æ 1
seancorfield02:11:44

@lilactown Or publish a gist (which can have multiple files and can be consumed as a git dep, at least by the CLI)... for example... https://gist.github.com/seancorfield/6e8dd10799e9cc7527da5510c739e52f

āž• 2
roklenarcic09:11:52

If I want to capture the current namespace into a symbol, how do I do it? So imagine:

(def x (symbol ??? "system"))
I know thereā€™s *ns* but I donā€™t know if it always points to the right thingā€¦

vemv09:11:13

sure! (namespace ::_) always points to the right thing

roklenarcic09:11:36

I went with

(namespace `x)

šŸ‘ 1
roklenarcic09:11:08

but your solution is even better

Ed10:11:33

the reader will use the value of *ns* to fill in the namespace in both of those forms

(binding [*ns* (find-ns 'user)] [(read-string "::test") (read-string "`x")]) ;; => [:user/test (quote user/x)]
so I guess it depends on what you mean by "always points to the right thing" ... šŸ˜‰

West13:11:37

Iā€™m wondering what ways there are to clean this up. https://gist.github.com/wildwestrom/819879253abe69a3c3340f7cc3985224 This is my attempt at converting a dictionary written in XML into nice Clojure data. As you can see, the main bulk of the work is done by this one giant function called kanji-data.

schmee14:11:38

looks readable as is IMO

p-himik14:11:19

There are a few small things I would write differently, namely: ā€¢ Using -> when appropriate instead of ->> (more details at https://stuartsierra.com/2018/07/06/threading-with-style) ā€¢ Using transducers instead of creating lazy sequences ā€¢ Using cond-> where appropriate ā€¢ Not using type hints, like ^map (use either :post for that or just the docstring) ā€¢ Clarifying why sorted-map is used instead of just {}

šŸ‘ 1
p-himik14:11:41

But other than that, all is good.

West14:11:42

Iā€™ve never thought of most of these, but imma try them out.

West15:11:40

I used sorted-map to maintain the order of the elements. I donā€™t think that was necessary. Changed it to this:

(fn [x] {:reading x :type "ja_nanori"})
Is there a way to write this with a shorthand fn? This does not work, and I donā€™t know why.
#({:reading % :type "ja_nanori"})

p-himik15:11:54

No, there's no shortcut. Your fn form is the shortest one. You can create a macro for that, but I wouldn't do it given that it would make it much more opaque while saving just a few characters.

West15:11:52

Alright cool, I donā€™t mind using fn anyway. I kinda like how this one turned out

(cond-> {val-key (first (:content x))}
  (and (some? attr-key) (some? attr-lookup))
  (conj {attr-key (attr-lookup (:attrs x))}))

p-himik15:11:32

If you know for sure that attr-key and attr-lookup can never be false, then just drop some? and use the values as they are. Also, I wouldn't use conj to add a key to a map - the most common approach is to use assoc . The intent is much more clear given that conj works on anything and assoc works only on associative collections.

West15:11:30

Iā€™m really checking if attr-key and attr-lookup are not nil, which they can be.

West15:11:42

Also good point about asssoc.

p-himik16:11:26

I understand that about nil. My point is that if the false value is not in the domain of variable x, (some? x) can be replaced with just x since there are only two false-y values in Clojure - false and nil.

West16:11:53

Itā€™s true that my attr-keyĀ andĀ `attr-lookup` will never be false, but why wouldnā€™t I use some?? Itā€™s the same as (not (nil?)) right?

(and nil nil)
;; => nil
(some? nil)
;; => false

West16:11:04

Anyway, I could also go with this:

(defn elem->maps
  [element {:keys [attr-key attr-lookup val-key]
            :or {val-key :val}}]
  (mapv (fn [x]
          (cond-> {val-key (first (:content x))}
            (not-any? nil? [attr-key attr-lookup])
            (assoc attr-key (attr-lookup (:attrs x)))))
        (vec element)))
or this:
(every? some? [attr-key attr-lookup])

p-himik16:11:42

Because in that case (some? x) is exactly the same as just x. But it's an extra form and an extra check. Instead, you can write:

(cond-> {val-key (first (:content x))}
  (and attr-key attr-lookup)
  (assoc attr-key (attr-lookup (:attrs x))))

West16:11:10

Oh, I get it! cond will read nil as false. I thought for some reason it would behave differently.

p-himik16:11:54

Not just cond or cond-> in this case. Everything in Clojure that accepts a boolean value will treat nil as false , except for false?.

West16:11:38

Right, makes sense.

West16:11:02

beautiful

šŸ‘ 1
emccue15:11:38

Does anyone have any experience using Server Sent Events?

emccue15:11:19

Iā€™m looking in to them as a potential mechanism for sending particular users prompts for feedback

Ben Sless16:11:37

If you're waiting for replies a websocket may be more appropriate. SSE is mostly for unidirectional communication, like subscribing on stock price updates

emccue17:11:41

its not live chat, just sending the prompt. a reply can come as a normal http request

Ben Sless17:11:20

But then both you and the client have to do lots of bookkeeping

emccue18:11:13

not really, also web sockets have spotty support depending on the browser

Ben Sless18:11:38

With SSE you keep a connection open, you have to ensure the client doesn't disconnect, and you depend on the client's implementation, too. As a server you can't initiate it, it has to be initiated by the client anyway. Both solutions have trade offs, but in terms of implementation SSE feels more annoying to deal with

lilactown19:11:08

i have never enjoyed using SSE

šŸ˜ž 1
lilactown19:11:22

between bad implementations on the server, and bad implementations on the client

Ola Sikstrƶm20:11:56

> not really, also web sockets have spotty support depending on the browser Really? I mean, all major browsers, including IE 10, have supported WS since 2012. What browsers still have bad support in 2021?

Joshua Suskalo17:11:26

Is there any way to look (temporarily) at the outer binding of a dynamic variable from inside a bound-fn ?

lilactown17:11:24

not sure but you could bind it to another var or local binding before using bound-fn

Joshua Suskalo17:11:44

Yeah, that doesn't help here in this case, but it's something I'll keep in mind if I run into something similar in future.

Joshua Suskalo17:11:15

The current problem I don't have control over the consuming code that's actually writing the bound-fn call, I'm just writing some code that wants to be aware of if it's inside of one.

Joshua Suskalo17:11:32

I think I was able to figure out a solution to my actual problem by just using a little constrained mutability though.

martinklepsch19:11:10

Trying to figure out a little data transformation puzzle: ā€¢ I have a list of strings with a start 0 and end 1 marker ā€¢ Sometimes I get them split up by the API Iā€™m working with, e.g. ["0a1" "0b1" "0c" "c1" "0d1"] ā€¢ And I want to transform this list so that I get ["0a1" "0b1" "0cc1" "0d1"]

Apple19:11:47

join the list then use regex

Apple19:11:49

(->> (clojure.string/join ["0a1" "0b1" "0c" "c1" "0d1"])
     (re-seq #"0.*?1"))

qqq19:11:39

Consider the problem of String -> AbstractSyntaxTree. Solving this problem is called Parsing, and we can solve it via Regex + LL / LALR / Peg / Parsec / ... . In particular, we can view Regex / Peg as "DSLs" for String -> AbstractSyntaxTree Now, suppose we are trying to define a DSL -- which can be viewed as a subset of s-exprs. For example, Scheme is a "s-expr DSL"; Lisp is a "s-expr DSL", edn is a "s-expr DSL", clojure is a "s-expr DSL" -- in all 4 cases, all valid data values look like s-exprs. Question: What is the Regex / Peg equiv for doing "s-expr -> DSLs" ? I.e. what is a "pattern description language" for specifying what subset of s-exprs are valid exprs in the DSL ?

hiredman19:11:14

Spec does this

martinklepsch20:11:09

@zengxh That works but I forgot to mention that this is all async and that I canā€™t just have ā€œthe entireā€ list

hiredman20:11:11

Infact, parts of spec internal use a parsing algorithm, just over elements in a sequence instead of tokens from a stream

martinklepsch20:11:19

What Iā€™m trying to achieve is definitely possible with reduce but Iā€™m trying to maintain the sequential format since I want to eventually emit those events as they become available

dpsutton20:11:00

a stateful transducer on a channel sounds straight forward to me

hiredman20:11:22

partition-by

dpsutton20:11:26

depending on the possible error conditions i guess. not sure what invariants and guarantees your data has

martinklepsch20:11:49

I donā€™t think partition-by works here though? Like Iā€™m assuming you mean to use it to get to something like ["0a1" "0b1" ["0c" "c1"] "0d1"] but Iā€™m not sure how Iā€™d do that with partition-by?

dpsutton20:11:26

(partition-by (fn [x] (re-matches #"0.*1" x)) ["0a1" "0b1" "0c" "c1" "0d1"])

dpsutton20:11:52

(("0a1") ("0b1") ("0c" "c1") ("0d1")). that seems straightfoward to me. then you just join the collections and youā€™re in business

dpsutton20:11:11

(let [pat #"0.*1"]
  (into []
        (comp (partition-by (fn [x] (re-matches pat x)))
              (map #(apply str %)))

        ["0a1" "0b1" "0c" "c1" "0d1"]))

["0a1" "0b1" "0cc1" "0d1"]

martinklepsch20:11:20

alright, that does seem to work with the broken down example but Iā€™m afraid I broke it down a bit too much.

martinklepsch20:11:43

In reality the chunks can be cut apart pieces of a bigger string like

some thing


another thing
this also belongs to another thing


yet another thing
belongs to above line, 2 newlines are the separator


and so on...

dpsutton20:11:24

where are the 0s and 1s?

martinklepsch20:11:21

I might have lied a little bit (unintentionally). Basically thereā€™s only a single separator not a start and end marker.

martinklepsch20:11:41

And the single separator is \n\n

dpsutton20:11:31

and its a stream of values split by newline?

dpsutton20:11:01

["some stuff" "\n" "\n" "another entry" "\n" "\n"]?

martinklepsch20:11:08

no it could be split like

some thing


another thing
this also belongs to another thing


yet another thing
belongs to above line, 2 newlines are the separator


and so on...

martinklepsch20:11:45

i.e. in this case the split sits ā€œinsideā€ the string I want to extract

dpsutton20:11:04

@hiredman check this for a more fleshed out representative problem

hiredman20:11:15

still not a great description of the problem, but it sounds like it reduces to the same problem

hiredman20:11:33

you just need to preprocess to turn it in to the same thing

hiredman20:11:07

mapcat splitting when it encounters something with two newlines, or whatever

hiredman20:11:07

(-> s (mapcat #(interpose "\n\n" (.split % "\n\n"))) ...the-partition-by-stuff...)

martinklepsch21:11:19

this is a sample of inputs

["thing 1\n\n"
  "thing2: "
  "also thing2\n\n"
  "thing3\n\n"]
and the goal:
["thing1\n\n"
 "thing2: also thing2\n\n"
 "thing3\n\n"]

martinklepsch21:11:35

So (.split % "\n\n") doesnā€™t really do much in this case I think

martinklepsch21:11:07

sorry for my poor articulation of the problem, I tried to simplify it but some properties got lost

hiredman21:11:50

user=> (->> data (mapcat #(if (.contains % "\n\n") [% :split] [%])) (partition-by (partial = :split)) (remove #{[:split]}))
(("thing 1\n\n") ("thing2: " "also thing2\n\n") ("thing3\n\n"))
user=>

martinklepsch21:11:01

thatā€™s smart

dpsutton21:11:02

i think that fails if one entry contains multiple broken "entry 1\n\nentry2\n\nentry3\n"`ā€œcontinuing entry3\n\nentry4ā€`

hiredman21:11:49

it does, which is what the interpose stuff was about up above, interpose isn't entirely right alone either, you need to interpose (in case of internal splits) and append a split at the end

martinklepsch21:11:03

I think that might not happen

martinklepsch21:11:09

spoke to soon, that does happen šŸ˜…

martinklepsch21:11:10

but I think I can figure that out now šŸ˜›

martinklepsch21:11:53

Thanks for your help Kevin and Dan!

qqq20:11:54

@hiredman: is there a unified theory behind spec, or is it a collection of really useful battle tested tricks glued together ?

hiredman20:11:14

I would ask in #spec

hiredman20:11:29

#clojure-spec

hiredman20:11:08

The parsing algorithm that is used to some degree internal in spec is parsing with derivatives

hiredman20:11:35

there were/are other clojure libraries that predate spec that define regexs over sequences

qqq20:11:21

Thanks, I posted over in #clojure-spec

Apple20:11:29

async? then you have a state you need to manage.

martinklepsch20:11:41

Iā€™m using #missionary which in theory allows me to manage that state in a transducer but havenā€™t worked it out yet šŸ™‚

thom20:11:45

I do basically exactly this with Gloss and Manifold streams (in a TCP context but no reason it can't work with pure data).

martinklepsch20:11:20

@thom704 interesting, the context Iā€™m in is Server-Sent-Events and consuming those using js/fetch

thom20:11:58

Ah, no use to you outside the JVM currently, sorry.

martinklepsch17:11:24

Seems like the implementation might be easy to share

hiredman20:11:06

abstractly it is a kind of self join

hiredman20:11:25

user=> (def data ["0a1" "0b1" "0c" "c1" "0d1"])
#'user/data
user=> (distinct (for [x data y data :when (= (re-matches #"0.*1" x) (re-matches #"0.*1" y)) :let [joined (hash-set x y)] :when (and (some #(.startsWith % "0") joined) (some #(.endsWith % "1") joined))] joined))
(#{"0a1"} #{"0b1"} #{"0c" "c1"} #{"0d1"})
user=>

hiredman20:11:06

using partition-by is an optimization based on the data being "sorted"

jt20:11:50

Hi, how do I insert a map into a vector of maps at a specific location

(defn insert-before [vector label-key value]
...
)
user => (insert-before 
  [{:label "A" :val "foo"}
   {:label "C" :val "baz"}]
  "C"
  {:label "B" :val "bar"})

[{:label "A" :val "foo"}
 {:label "B" :val "bar"}
 {:label "C" :val "baz"}]

p-himik20:11:12

By using a combination of subvec and into.

(let [insert-at-idx (...)
      v [...]
      item ...]
  (-> (subvec v 0 insert-at-idx)
      (conj item)
      (into (subvec v insert-at-idx))))

jt21:11:11

thanks I implemented insert-at-idx function just prior but I realized that I wonā€™t have the index when I need to call the function are you suggesting implementing my function insert-before by composing insert-at-idx and a get-index function?

p-himik21:11:41

I don't know what "insert-at-idx function" is - in my snippet above it's the index itself.

jt21:11:17

what is the function to get the index? in the example above the element with :label "C" is 1

sgepigon22:11:08

Alternative implementation using split-with:

(defn insert-before [coll label v]
  (let [[before after]
        (split-with (comp (complement #{label}) :label) coll)]
    (-> (vec before)
        (conj v)
        (into after))))

jt22:11:50

perfect! thanks @U56NS7GMQ

šŸ‘ 1