Fork me on GitHub
dang duomg 19102:12:23

how do i use tranducers

dang duomg 19104:12:34

@U11BV7MTK i read it but couldn’t understand nvm i read it again and got it thanks anyway

👍 1

When transducers came out, Rich Hickey gave two talks: 1. 2. If you want to really simplify them, the first explains "why" and the second "how". Perhaps they will help you as much as they helped me. :]


I also find Fred Overflow's videos on Youtube great to learn about transducers: If you happen to be doing Advent of Code as well, many times he ends up refactoring his solutions to use transducers and I think that may have helped me understand them the most since I had solved the problems as well.


A styling question: There are a number of methods that one can use to wrap many-arguments functions with in named facades, namely, 1. With def + lambda (def specialisation #(generic-fn "val1" "val2" %)) 2. With partial (def specialisation (partial generic-fn "val1" "val2")) 3. With defn (defn specialisation [last-arg] (generic-fn arg1 arg2 last-arg)) I have looked in and didn’t see this being addressed. Alex has that he’d prefer anonymous fns to partials, but that answer was in a pretty different context. This is quite a subjective question by I would still love to know if someone has already written down some thoughts on this that I missed. Otherwise what would you personally prefer and why?


defn > #() > partial because top level functions much easier to refactor and work with (kudos to clojure-lsp and cider) i prefer anonymous functions over partial because it is easier to read and understand which argument guess where

Alex Miller (Clojure team)13:12:22

Importantly here , partial captures the value of generic-fn at definition time so won’t see redefinition at the repl (as it doesn’t use the var)

👍 1
Alex Miller (Clojure team)13:12:07

And I would agree with that preference for defn over the others


great point on capturing the fn value, hadn't thought about that


Another big plus for defn is docstrings and parameter name metadata. Once you define something with defn, your editor can provide this documentation to you automatically when using the function. It is possible to get at this metadata, and thus theoretically pass it on for consumption by tooling. But the straightfoward solution is to just wrap up what would be def'd function inside a defn'd function that provides the docstring and names for the parameters.

Paul Vallance09:12:21

Hello, am getting started for first time trying to use Calva with Windows 10 and VSCode. If I run the Calva ' getting started REP' from the VSCode command prompt it works fine and the interpreter runs. However, if i create my own local files and try to run a REPL on it using jack in nothing happens. I spoke to the calva developer but no dice! Have searched Youtube and the web for a basic getting started demo and frankly it's all pure 'geekdom'. There appears to be no entry level explanation.

Robert Todea10:12:40

Are you using wsl2 or Windows directly?


Calva dev here. I wouldn't summarize the talk as ”no dice” :smile: but indeed, this can be tricky to get working, for reasons unknown to me. I suggest to try these two things, regardless if the first one works or not: *1. Try without `clojure*:` a. Remove your install of `clojure` from your computer. b. Check that the Getting Started REPL still works c. Check that you can use Calva to start the repl (jack in) to a basic deps.edn project. ”Basic” meaning it has this structure: ```. ├── deps.edn └── src └── hello.clj``` Where `deps.edn` has this content: ```{}``` `src/hello.clj`: ```(ns hello) (defn hello-world [] (println "Hello World") "Hello World") (hello-world)``` After jack-in, open the `src/hello.clj` file and use the command *Calva: Load/Evaluate Current File and its dependencies*. You should see this in the output/REPL window: ```; Evaluating file: hello.clj Hello World "Hello World"``` *2. Use `clojure`, without Calva* a. Install `clojure` per <|these instructions> b. With a terminal in the project root of ^that^ simple project, issue the command `clj` c. Try the following: ``` % clj Clojure 1.11.1 user=&gt; (load-file "src/hello.clj") Hello World "Hello World" user=&gt; (in-ns (in-ns 'hello) #object[clojure.lang.Namespace 0x5bb8f9e2 "hello"] hello=&gt; (hello-world) Hello World "Hello World" hello=&gt;``` Your output should look very similar to mine. If experiment 2 fails, then something is up with how `clojure` is installed on your machine. It is the same on my two Windows machines, so I think it might be quite common. There is a channel for <#CFN4QDHPS|> where you might get tips on how to proceed. One way is to use the Windows binary of deps.clj from here: <> and rename it to `clojure` somewhere in the PATH. (Instead of the ”official” command.) It's what I do on Windows (which I don't use often at all, so I am a bit at loss trying to help you, unfortunately.) In any case, experiment 2 must succeed for Calva to be able to use the `clojure` binary. If 2 works, but you can't jack-in with Calva, then we have a bug (or you might need to start VS Code from a terminal where the `clojure` command works). Please also feel welcome to ask for help in the <#CBE668G4R|> channel.


One more thing. @U04CGV9PKQD, there is a channel where we try to figure on ways we can improve the starting ramp for Clojure. Please add your experience and thoughts about it there: #C02CPM2R1KK

Paul Vallance13:12:47

Hi, thanks for feedback. Am using Windows directly. I finally found an idiots guide on Youtube for Calva and VSCode (for Windows) and managed to install Leiningen. Am assuming that Leiningen sets up new projects with correct directory structure so that my source and deps.edn files can be read correctly by VSCode. Unfortunately too little knowledge is a bad thing!


Leiningen is another tool for launching Clojure (and more). Generally speaking, it is clojure or lein. Where clojure is from the same team as provide Clojure, and Leiningen is a community contribution, which has been around for much longer than clojure has (I think). Leiningen has a better Windows story, I also think. You probably won't start with using deps.edn for dependencies, because Leiningen has its own system for that, which will go into project.clj, the project file for Leiningen projects. Leiningen is wonderful, if that works for you, that is great. When you really need to use clojure , e.g. you want to contribute to some deps.edn based project, you can solve this mystery on your machine then.

Paul Vallance14:12:37

I followed Pez's instructions above and on running the jack-in command VS code asks me which project type..'Babashka, nbb or joyride?', none of which work and the jack-in fails.. The idiots guide for Windows and Calva on Youtube i am following is using Lein to set up the project so I will follow that religiously and see where it gets me.


I think that you must have missed something in the instructions, because in addition to those project types you mention, it should offer the project type deps.edn.


VS Code is project folder based. So you need to have window with that basic project's root opened as the folder for the instructions to work.

Paul Vallance14:12:46

ok thanks for the info. No DEPS.EDN offered as project. Got it working now using Leiningen. At cmd prompt I use lein new project, then in the created project directory typed code . and the project opened in VSCode. I opened the core.clj and ran the jack-in command, which asked for the project type ( which is leiningen) and all works fine... Going to go through the tests in th LISP book, familiarise with VSCode and come back to the deps.edn issue later. Thanks again for all your help


So, sorry for nagging, but if you place a deps.edn file in that Leiningen project root. And you jack-in, you don't get offered to jack-in to the deps.edn project type?


If dealing with a huge EDN map, is it possible to do lookups without loading the whole thing into memory?


I'm not aware of any built in way to do this. Postgres can import data from arbitrary sources (e.g. CSV files). It's called foreign data wrappers and I think edn files are can be supported


If your edn files are so big that they don't fit in memory, maybe it's better to put them into some kind of a database anyway. If you can't do that you could split them into multiple files and run something like a bloom filter on each to find which contains the key you're looking for. I'm afraid I don't have any clojure-specific solutions though


Thanks, @UEQPKG7HQ. I too suspect a DB is the way to go. I was looking at using some lazy-map implementation like, but it looks like you still have to have an actual map at some point. It would be cool if you could evaluate maps in Clojure and only go a level deep or something so lookups could be light on maps with heavy values.


Oh, I assumed you wanted to split the map laterally, are you more worried about the depth of the map than it's width?


Not that I have any insights to offer based on this new information, lol


hah, yeah, moreso depth.


Maps aren't necessarily eager, if one of the nested values is a lazy sequence it will still not be evaluated unless you explicitly realize it. If you can represent all the values as lazy computations I think that you can have what you want :thinking_face: Interesting idea for a library 😄


All you need is to reimplement all the clojure.core functions that operate on maps (assoc, merge, etc) and establish a mapping between files and key paths, easy peasy!


It seems conceptually doable.


(merge {:a "a"}
       (when (= 1 1)
         (when (= 2 2)
           (if (= 3 3)
             {:foo "bar"}
             {:foo "nax"}))
         (when (= 3 3)
           (if (= 4 4)
             {:zzz "bax"}
             {:zzz "naz"}))))
this returns {:a "a", :zzz "bax"} but I want it to return {:a "a" :foo "bar" :zzz "bax"} can anyone explain why it behaves the way it does and apart from repeating
(when (= 1 1))
what would be the alternative way to write this? (it’s kind of nested so once I’ll get it working it’ll be separate functions)


You need to barf that last when expression out of the first when expression. Right now, the first when has two forms in its body and ignores the result of the first form, as you'd expect.


okay, but then I have to repeat the (when (= 1 1) part like this,

(merge {:a "a"}
       (when (= 1 1)
         (when (= 2 2)
           (if (= 3 3)
             {:foo "bar"}
             {:foo "nax"})))
       (when (= 1 1)
         (when (= 3 3)
           (if (= 4 4)
             {:zzz "bax"}
             {:zzz "naz"}))))
is there a way I can achieve the same without doing it?


a when contains an implicit do which will allow you to have side effects before returning the last value in it's body. So your when (= 1 1) will be true, and then evaluate the when (= 2 2) block for side effects and throw away the result, and then continue on to the when (= 3 3) block and return the result. You could re structure the code to pass the result through. Maybe something like this?

(merge {:a "a"}
         (when (= 1 1)
           (-> {}
               (cond-> (= 2 2) (assoc :foo (if (= 3 3) "bar" "nax")))
               (cond-> (= 3 3) (assoc :zzz (if (= 4 4) "bax" "naz"))))))

☝️ 1
👍 1

or you could use a let block to name some of the values so you're not repeating them?

👍 1

thanks for the explanation!

👍 1

you can use another merge inside of your when


(merge {:a "a"}
       (when (= 1 1)
         (merge ;; new to combine the two subforms
          (when (= 2 2)
            (if (= 3 3)
              {:foo "bar"}
              {:foo "nax"}))
          (when (= 3 3)
            (if (= 4 4)
              {:zzz "bax"}
              {:zzz "naz"})))))
{:a "a", :foo "bar", :zzz "bax"}


right, this also works! didn’t know we could do that 👍


you’re familiar with merge obviously. So I’m just using it to merge two maps. It’s the same strategy you’re using at the top level but at a lower level


yup, makes sense! thanks

👍 1

There’s also

(apply merge [{:a "a"}
              (when (rand-nth [true false]) {:b "b"})
              (when (rand-nth [true false]) {:c "c"})])
;;=> {:a "a", :b "b"}

catjam 1

Hey Everyone, Need some help with Java Interop, Trying to write Clojure version of this class --> Mainly the Cluster and GraphTraversalSource. I also had a look at Orge, Tinkerpop library for Clojure. But Couldn’t find any examples to connect to remote Gremlin Server. Any help will be much appreciated. Thanks!


What does your current code look like? How is it not working and where you got stuck?


I don’t have the appropriate class to work with, so I’m just typing raw text, but this should be along the right path:

(ns your.nmspace
  (:import [java.util.function Function]
           [org.apache.tinkerpop.gremlin.driver Cluster]
           [org.apache.tinkerpop.gremlin.process.traversal.dsl.graph GraphTraversalSource]
           [org.apache.tinkerpop.gremlin.process.traversal AnonymousTraversalSource]
           [org.apache.tinkerpop.gremlin.structure T])

(defn ^Function jfn
  (reify Function
    (apply [this arg] (f arg))))

(defn do-the-thing
 (let [builder (doto (Cluster/build)
                     (.addContactPoint your-neptune-endpoint)
                     (.port 8182)
                     (.enableSsl true))]
  (with-open [cluster (.create builder)]
    (let [^GraphTraversalSource g (doto (AnonymousTraversalSource/traversal)
                                        (.withRemote (DriverRemoteConnection/using cluster)))]

      ;; Add a vertex.
      ;; Note that a Gremlin terminal step, e.g. iterate(), is required to make a request to the remote server.
      ;; The full list of Gremlin terminal steps is at 
      (-> g
          (.addV "Person")
          (.property "Name" "Justin")

      ;; Add a vertex with a user-supplied ID.
      (-> g
          (.addV "Custom Label")
          (.property T/id "CustomId1")
          (.property "name" "Custom id vertex 1")
      (-> g
          (.addV "Custom Label")
          (.property T/id "CustomId2")
          (.property "name" "Custom id vertex 2")
      (-> g
          (.addE "Edge Label")
          (.from (.V g "CustomId1"))
          (.to (.V g ("CustomId2")))

      (let [^GraphTraversal t (-> g (.V) (.limit 3) (.elementMap))]
        (.forEachRemaining t (jfn #(println (.toList %))))))))


Apparently, I have been out of Java for too long, because I don’t recognize the syntax of __.V(). I figured the __ would be a variable in scope somewhere, but I can’t find it. However, the only thing I can see that has a .V() method is the GraphTraversalSource, so I guessed it was that.


Some of this is going to use lots of reflection, which is why I brought in some type hints (there are one or 2 left though)


@U051N6TTC thank you! I’ll give this a try.


is there a way to rerun my tests if my source file changed?

Panu Puro17:12:10

You might want to take a look at Koacha. It has such a feature.

👍 1

bin/kaocha --watch                                                                                                             ✔  2314  21:37:31
Execution error (FileNotFoundException) at (
--watch (No such file or directory)


calling it with arguments should work though, right? If I try calling without --watch I just get a clojure repl

Alys Brooks20:12:52

I think there's an issue with how bin/kaocha is set up in your project.

Alys Brooks20:12:34

You might be missing the -m kaocha.runner part


I copied it from the github readme, same error message when trying clojure -M:test advent_of_clerk/day_06.clj --watch , but clojure -M:test src/advent_of_clerk/day_06.clj --watch works (at least it runs the tests once, but then exits and doesn’t continue watching)


will try with kaocha.runner

Alys Brooks20:12:49

Right, I forgot we did it with an alias in the README. Did you add the :test alias?


I tried at least, did I maybe get something wrong in my :aliases?

:aliases {:nextjournal/clerk {:exec-fn nextjournal.clerk/build!
                               :exec-args {:index "src/advent_of_clerk/index.clj"
                                           :paths-fn advent-of-clerk.index/build-paths}
                               :main-opts ["-m" "babashka.cli.exec"]}}
          {:extra-deps {lambdaisland/kaocha {:mvn/version "1.71.1119"}}
           :main-opts ["-m" "kaocha.runner" "unit"]}}


also added tests.edn so it picks up all tests

Alys Brooks21:12:13

:aliases looks fine. is advent_of_clerk/day_06.clj in your bin/kaocha? You don't need to put any paths there. Test paths are handled by tests.edn and you can use --filter to select a particular namespace.


If you don’t want to move to a new test runner you could use lein Auto And run lein auto test and it will run all your tests when the files change.


nothing in bin/kaocha besides the script :thinking_face:


I normally use leiningen, so I’ll check it out next time, thanks! is there something similar for deps projects?


Not that’s I’m aware off. I am not familiar with the Deps ecosystem

Alys Brooks17:12:03

@U043ZD1K5TL I was a little unclear what you're saying about running clojure -M:test advent_of_clerk/day_06.clj --watch . Happy to help further, but I think I would need the contents of bin/kaocha or clarification that you're using the README 's version as-is.


@U01FJUDL57C thank you, the content of my bin/kaocha are the two lines described here:


what I meant with the command above is that I tried using the alias directly instead of from the binstub and it worked when giving it the file path to a file with tests