Fork me on GitHub
#clojure
<
2023-12-29
>
migalmoreno00:12:20

Hi all! Thought I'd share a project I've been working on and off during the last year https://github.com/migalmoreno/tubo (Not sure if this is the right channel for self-promotion/contributions). It essentially aims at being a port of Newpipe, a front end to various streaming services, but for the web. It leverages the NewpipeExtractor Java library to gather the data, exposes it via a simple REST API and uses re-frame for the front-end SPA. It's my first time using Clojure(Script) in a personal project so any ideas/comments are welcome!

Noah Bogart00:12:57

Cool! #C06MAR553 is for new projects and big releases (major features, been a long time since last release, etc), and #C015AL9QYH1 is for smaller releases and minor updates

migalmoreno14:12:59

Thanks for the heads up! I'll post it there then

hifumi12307:12:59

This may be an XY problem, but I want to be able to have circular references in a namespace without forward declaring vars, so that I can take advantage of direct linking when AOT compiling. My use case involves declaring Reitit route data referencing a function that will perform reverse routing with such data. The reason this function needs reverse routing is because I want a single source of truth for generating redirect URLs — my route data. But the design of the Reitit library is such that the data must reference the function, so what I want is a circular reference. I can easily do this by simply declare-ing the function when I define my routes, but from what I understand, this makes direct linking of vars impossible. Should I just live with this fact? I have also looked into replacing this handlers with a multi-method dispatching on the :request-method of a request map, but this hinders the ability to use default routers, since Reitit will end up calling the multimethod for all HTTP request methods. For this reason, I have decided against multi-methods.

duckie 3
1
hifumi12307:12:47

OK typing this out made me realize what I really want is some Ring middleware that passes the router down to handlers. This made me look into Reitit documentation to see if there was something like this. Lo and behold, what I really want is this. https://cljdoc.org/d/metosin/reitit/0.6.0/doc/ring/reverse-routing#reverse-routing-with-ring

👍 3
chaos13:12:27

Hi, does anyone have any insights into why partitioning a map into {} results in the same map being returned? I initially expected it to fail because partition typically returns a seq over a seq of pairs, rather than a seq over the pairs themselves.

;; partition over a map comes back a seq over a seq of the map pair
  user> (partition 1 {:a 1})
  (([:a 1]))

  ;; and it unexpectedly to me gives back the same map when `into {}`
  user> (into {} (partition 1 {:a 1}))
  {:a 1}

  ;; if it is a vector over a vector of the same pair though fails (as expected)
  user> (into {} [[[:a 1]]])
  Execution error (IllegalArgumentException) at user/eval8353 (REPL:174).
  Vector arg to map conj must be a pair

  ;; a vector over the same pair works as expected 
  user> (into {} [ [:a 1] ])
  {:a 1}

  ;; a seq over a seq of the same pair
  user> (seq [ (seq [ [:a 1] ] ) ])
  (([:a 1]))

  ;; the same initial example with seq fails, so why is partition works at all?
  user> (into {} (seq [ (seq [ [:a 1] ] ) ]))
  Execution error (ClassCastException) at (REPL:1).

p-himik13:12:50

into is just a reduce with conj. conj'ing a kv-pair onto a map is equivalent to assoc'ing those k-v.

p-himik13:12:39

(into {} [[[:a 1]]]) fails because you have an extra level of nesting. Remove one pair of [...] and it'll work just fine, as you have demonstrated yourself.

p-himik13:12:14

Oh, my bad - didn't notice that initially there's an extra nesting as well. It works because conj'ing a sequence onto a map works if that sequence is a sequence of map entries specifically (so a 2-item vector won't work).

chaos14:12:45

why isn't the last example working though? it's the same as the partition collection, a seq over a seq over same pair

p-himik14:12:30

The partition function returns a sequence of partitions, where each partition has only proper map entries - instances of the Map.Entry class. The last example creates tuples - they are not instances of that class.

p-himik14:12:39

That's why you see ClassCastException.

chaos14:12:05

Right, thanks, I'd say this appears to be an implementation coincident rather than a design feature

user> (seq [ (seq [(clojure.lang.MapEntry/create :a 1)])])
(([:a 1]))
user> (into {} (seq [ (seq [(clojure.lang.MapEntry/create :a 1)])]))
            
{:a 1}

p-himik14:12:56

Indeed, I think it's to allow smooth interoperability with sequence functions.

👍 1
Alex19:12:00

Can you suggest some good resources to get started with non-trivial macros (DSLs)? It feels like I'm hitting some barrier, which I need to overcome first to achieve anything meaningful...

p-himik20:12:49

How's that relevant to the question?

Alex Miller (Clojure team)20:12:15

I find that a lot of people skip the first step, which is thinking about how to do stuff with data and functions. Do that first (it will be verbose), then write the macros to transform syntax into that

Alex Miller (Clojure team)20:12:14

Data and semantics first, syntax last

Alex Miller (Clojure team)21:12:48

This is a super old talk but still an extremely good one in this area (and Christophe is a master at this) https://youtu.be/3yvrs9S0RIw?feature=shared

clyfe23:12:46

@U2FRKM4TW Writing non trivial macros is a great use case for clojure.spec (conform, transform, unform), hence the example.

p-himik23:12:57

Isn't that quite limiting? The transform function would only be able to return something that conform with that spec can return, otherwise the contract of unform is broken, even if things end up working just fine.

clyfe23:12:14

It does limit the input to the spec conforming forms; sometimes that's what you want.

Alex19:12:08

There's a great deal of wisdom in Christophe's words, but I've got an actual assignment to make a macro for an already defined DSL :rolling_on_the_floor_laughing:

p-himik19:12:16

But what is the problem? If it's with macros, we're happy to help if you provide some specifics. If it's with transforming some particular DSL into something else - same thing. But "non-trivial macros for DSL" without any details isn't that useful, even as a grounds for recommending learning resources.

Alex19:12:50

Fair. I'm just hitting compiler errors all the time, so I thought I'm doing something completely wrong. Looking at some examples shared here, it's not that different, so there must be some small but a key detail I'm missing. I borrowed an (old) book from a friend to help myself. If that won't suffice, I'll ask about specific errors I'm getting. Thank you!

p-himik19:12:12

If there's an example that fails that's small enough, sharing it won't hurt. Might get you up to speed much faster than reading a whole book (although the latter also won't hurt if you're not in a hurry).

🙏 1
Jakub Holý (HolyJak)22:12:20

Hello! I am trying to apply https://clojure.org/guides/dev_startup_time, which suggests > The compile function takes a namespace symbol and compiles that namespace and all the namespaces it requires into *compile-path* but in my case it only compiles the given namespace itself, and none of its dependencies. I.e. inside my compile-path, which is target/classes/, I only see my/ns//*.class. How is that possible? What am I missing? Clojure 1.11.0 🙏

1
hiredman22:12:22

What does the ns form of your namespace look like?

hiredman22:12:58

Oh, likely you already loaded everything

hiredman22:12:40

Compilation works as a side effect of loading code, so if you have already loaded stuff you can get weird behavior when trying to compile

Jakub Holý (HolyJak)23:12:13

> Oh, likely you already loaded everything > Compilation works as a side effect of loading code Oh, that must be it, thanks a lot! That wasn’t really clear to me 😅 Then I will try to (require <ns> :reload-all) workaround…

Jakub Holý (HolyJak)23:12:42

Hm, I still do something wrong. I have tried

(do (compile 'user)
      (binding [*compile-files* true] (require 'user :reload-all))
      (compile 'user))
yet still nothing is compiled… (my user.clj is on the classpath, and loads many namespaces) (I believe the compile calls are unnecessary, setting the dyn. var. should be enough)

Jakub Holý (HolyJak)23:12:14

Ah, I got it working now, after I replaced user with my init ns: (binding [*compile-files* true] (require ' :reload-all))

hiredman23:12:56

I would not trust compiled from reloaded, it is very easy to end up class files written to disk that depend on the previously compiled code that only exists in memory

hiredman23:12:40

And some libraries just don't work right with reloading

Jakub Holý (HolyJak)23:12:50

Ok, so the best approach then is to have an explicit compile process / step , instead of trying to trigger it from the REPL, right?

hiredman00:12:16

Depends on your repl, the stock repl doesn't load anything (other than user.clj) by default, so if you use the stock repl and don't have a user.clj you are fine

👍 1