Fork me on GitHub
#beginners
<
2018-10-16
>
chrisulloa00:10:38

So I was experimenting with writing the following function into a transducer. The function takes in a collection of geographic points then returns a collection where each point is guaranteed to be x distance away from any other point. The input collection is larger than the output collection because I get rid of any points that are too close together. First I create the RTree, then put a point into the tree. On the next points, I check to see whether it's near any of the points already in the tree. If it is far enough away I put it into the tree, if not I discard it. Through function composition, I also want to do other transformations on the points in addition to spacing them out.

chrisulloa00:10:10

(defn space
  ([distance]
   (fn [rf]
     (let [treev (volatile! (RTree/create))]
       (fn
         ([] (rf))
         ([result] (rf result))
         ([result input]
          (let [point (seed->point input)]
            (if (nearby-point? point @treev distance)
              result
              (do (vswap! treev .add nil point)
                  (rf result input)))))))))
  ([distance seeds]
   (loop [tree (RTree/create)
          seeds seeds
          spaced-seeds []]
     (if (not (empty? seeds))
       (let [seed (first seeds)
             point (seed->point seed)]
         (if (nearby-point? point tree distance)
           (recur tree (rest seeds) spaced-seeds)
           (recur (.add tree nil point)
                  (rest seeds)
                  (conj spaced-seeds seed))))
       spaced-seeds))))

chrisulloa00:10:38

What I thought was really interesting was the idea of focusing on the transformations in terms of an individual element of the collection. And in order to know whether to keep or point or not I need to maintain a state, kind of like how distinct or dedupe work.

noisesmith00:10:53

so the transducer here doesn't pass on the structured tree, it is merely a filter for items that fit in the tree

noisesmith00:10:19

also, I don't think you strictly need a collection here (despite your usage of the term in the description), but the main point of transducers is not relying on the existence of some collection - making that an implementation detail you don't need to care about

chrisulloa00:10:31

Right I'm using the RTree as a means to filter things. I don't want to store and retrieve from the RTree. I see what you're saying in the second comment.

noisesmith00:10:10

I don't think that has any relevance to your design / the code as written, just a note about the description

👍 4
noisesmith00:10:57

I wonder how hard it would be to rewrite the transducer in terms of the filter transducer, with a function that does the tree check / update as appropriate? I don't know if the point here was to go through the exercise of making a transducer, but just using filter might be simpler.

noisesmith00:10:57

also the loop in the non-transducing case would be simpler as a reduce - you can use a hash-map or even a vector to represent a multi-part state in reduce

noisesmith00:10:59

another interesting exercise would be making the lazy version - it would look a lot like the loop version

chrisulloa00:10:45

I guess it is similar to filter because I have a predicate that determines what to pass along, but I need the additional code to maintain the state of the RTree.

noisesmith00:10:14

right, but the state of the RTree is already a side effect, it can fit in a filtering function

noisesmith00:10:48

the hard part would be if the output should / could contain a false or nil value, then basing it on filter would break

noisesmith00:10:10

basing it on filter would also be a simple (but less instructive) way to make the lazy version - don't use filter to do the lazy version, as doing the RTree as a side effect with a volatile would break assumptions about lazy-seqs

noisesmith00:10:35

but luckily laziness via recursion is straightforward, and allows using the RTree functionally as the gods intend

chrisulloa00:10:49

Oh I see, those are interesting things to think about (admittedly still trying to wrap my head around some of what you're saying). But I will definitely give making a lazy version a shot, and trying the reduce suggestion.

chrisulloa00:10:14

I definitely agree with that last comment lol

chrisulloa00:10:06

Oh I see how some other functions like remove use filter to build a transducer.

chrisulloa00:10:10

That is pretty neat

andrewzhurov11:10:55

Hi, guys! 👋 I'm developing an SPA on reagent+re-frame, with reactive data flow, event-driven plus TDD http://dev.brawl.haus It's meant to be a platform for multiplayer games, challenges etc - some fun stuff There are two games yet though and those are more a tech demo of what can be done I thought on going live in 20 minutes, 12 am GMT at Twitch: https://www.twitch.tv/elvis_prevsley Would love to have people around to talk along the way!

cristibalan16:10:42

Heys. That sounds fun, but it looks like I've missed it. I hope you had fun. Could you enable "Store past broadcasts" (they get saved for 14 days)? https://www.twitch.tv/elvis_prevsley/dashboard/settings

Bobbi Towers19:10:08

cool, I've been meaning to get started with twitch so I followed so I can hopefully catch you next time

andrewzhurov20:10:31

see you on next rounds, guys.:)

samcgardner13:10:00

I'm having trouble reading a value from a map using a string key supplied as a function parameter. Does this make sense?

(defn parse-command
  [commands input]
  (let [split (s/split input #" " 1)]
       [first split [0]]
       [second (fnil split [1] "")]
    (partial (get commands first fallback-command)  second)))
The following test always retrieves my fallback:
(deftest parses-command-and-args
  (testing "Returns a command with its arguments supplied when it is in the command map"
    (let [commands {:dummy-command (fn [a _] {:dummy a})}
          parsed (parse-command commands "dummy-command 5")
          result (parsed {})]
      (is (= {:dummy 5} result)))))
EDIT: For anyone interested, the following fixes were needed: 1. I made a mistake in my first function, it should read
(defn parse-command
  [commands input]
  (let [split (s/split input #" " 2)
        first (nth split 0)
        second (nth split 1 "")]
    (partial (get commands first fallback-command)  second)))
Secondly, you need to use string keys when you're feeding in a string:
(deftest parses-command-and-args
  (testing "Returns a command with its arguments supplied when it is in the command map"
    (let [commands {"dummy-command" (fn [a _] {:dummy a})}
          parsed (parse-command commands "dummy-command 5")
          result (parsed {})]
      (println parsed)
      (is (= {:dummy "5"} result)))))

cristibalan16:10:41

Hi. Here are two alternative implementations of your parse-command. You could use the built in first and second functions.

(defn parse-command
  [commands input]
  (let [split (s/split input #" " 2)]
    (partial (get commands (first split) fallback-command) (-> split second str))))
Or you could use destructuring.
(defn parse-command
  [commands input]
  (let [[comm args] (s/split input #" " 2)]
    (partial (get commands comm fallback-command) (str args))))

samcgardner18:10:48

Thanks, that destructuring version looks much simpler 🙂

Michael Fiano17:10:22

Is anyone here familiar with the gloss library for parsing binary formats?

Michael Fiano17:10:14

As my first bit of Clojure code, I'm attempting to use it to parse the FLAC audio format. In the docs, it mentions the use of repeated, but I can't figure out how to terminate on a frame's field value of what it reads. Here is my code, and line 47 shouldn't be a hard-coded stream-info block, but it should repeat until the :last? field of the header it reads is 1: https://gist.github.com/mfiano/04117773cc90e24845854aa32b9e2301 I'd appreciate any help. As I mentioned, I am extremely new to Clojure, and the docs for that library are a little lacking. I'm unsure if it lacks support to pull this off or not. Edit: wrong link and line number

Nikita Vasilchenko18:10:44

Hi! I have a trouble with my spacemacs cider plugin. It prints unreadable error log.

Nikita Vasilchenko18:10:22

But this program works fine with vscode clojure plugin.. So my REPL is fine)

noisesmith18:10:52

I bet vscode is picking a 1.8 jvm and cider is trying to use a newer one, based on the error messages

noisesmith18:10:46

either that is vscode doesn't trigger the problems that cider hits with your vm

noisesmith18:10:12

many of the common convenience tools break with newer jvms

Nikita Vasilchenko18:10:06

Thanks, I’ll try to dig in this direction)!

practicalli-johnny19:10:36

@UCRM55BKM If its not the JVM or something in your .lein/profiles.clj, it could be sayid debugger causing a problem. If you are using Spacemacs develop there was an issue, now fixed. Sayid and clj-refactor are now optional in Spacemacs develop, although can be easily added back https://practicalli.github.io/spacemacs/install-spacemacs/enhance-clojure-experience.html

practicalli-johnny19:10:21

Feel free to ask for more help in the #spacemacs channel

Nikita Vasilchenko19:10:59

@U0BEJGE9Y thank you, for your attention! I manually installed sayid now. but this error still exists. This error occurres when I run cider-jack-in

Nikita Vasilchenko20:10:16

in REPL buffers from the beginning

noisesmith20:10:45

what about lein repl in a terminal directly in the project?

rakyi18:10:24

@mfiano I’m not familiar with the library, but is take-while by any chance what you are looking for?

Michael Fiano18:10:34

@rakyi I'm not either, and I'm not really sure if the forms inside the defcodec macro are evaluated to do so. This is more of a declarative approach

samcgardner18:10:27

Given:

(defn score-command
  [args game]
  (let [[name score] (s/split args #" " 2)]
    ;; If we don't have a valid player and score, do not change the game state and print an error message
    (if (or (nil? name) (nil? score) (not (integer? (read-string score))))
      ((println "Valid score command not supplied") game)
      (let [parsed-score (read-string score)
            name-as-keyword (keyword name)
            highscore (max (get-in game [:players name-as-keyword :highscore] 0) parsed-score)
            update { :name name
                     :score parsed-score
                     :highscore highscore}]
        (assoc-in game [:players name-as-keyword] update)))))
This simple test
(deftest score-command
  (testing "Inserts a user score into an empty map")
  (let [result (score-command "Sam 5" {})]
    (println result)))
Throws the error "No value supplied for key: true". I understand this is due to having mistmatched keys in the map assignment, but I'm not sure where as they look matched to me. In fact, removing the map assignment altogether doesn't get rid of the error, so I'm really not sure what the source is. Is it apparent to anyone else?

hiredman18:10:29

((println "Valid score command not supplied") game) is a bug

Michael Fiano18:10:19

You can't have nil in operator position

hiredman18:10:20

turning things in to a keyword just to be a map key is not required

samcgardner18:10:12

How do I print and return for an error check then? i.e. (if (guard) (print-and-return) (do-my-stuff))

hiredman18:10:27

what is the rest of the stacktrace for your error?

hiredman18:10:54

"No value supplied for key: true" would happen when creating a map, which you aren't really doing there, so I suspect the error is happening somewhere else

hiredman19:10:24

it isn't an error running code, it is an error compiling it

hiredman19:10:58

it looks like maybe you have a 'true' in an ns form somewhere?

samcgardner19:10:36

As in, I literally have an identifier called "true" bound somewhere?

hiredman19:10:08

something like (ns foo (:require [:foo] true)) maybe (hard to reproduce if you have a clojure version that uses spec you get a different error)

samcgardner19:10:39

My ns is just:

(ns game.lib
  (require [clojure.string :as s
            clojure.edn :refer read-string]))

hiredman19:10:17

should be [read-string]

samcgardner19:10:55

Oops, so it should

hiredman19:10:32

also require should be :require

hiredman19:10:45

also each ns should be in a separate vector

hiredman19:10:15

you may want to switch to a newer clojure version

hiredman19:10:50

the ns macro does very little validation of its inputs, new clojure versions atleast have spec checking the inputs

samcgardner19:10:17

I'm perfectly happy to, I just use whatever lein gave me. Do I just up my dependency on org.clojure?

hiredman19:10:34

yep, 1.9.0 is I think the latest (not sure if there was a patch release)

hiredman19:10:48

org.clojure/clojure

samcgardner19:10:32

Exception in thread "main" clojure.lang.ExceptionInfo: Call to clojure.core/ns did not conform to spec:
In: [1] val: ((:require [clojure.string :as s clojure.edn :refer [read-string]])) fails spec: :clojure.core.specs.alpha/ns-form at: [:args] predicate: (cat :docstring (? string?) :attr-map (? map?) :clauses :clojure.core.specs.alpha/ns-clauses),  Extra input
 #:clojure.spec.alpha{:problems [{:path [:args], :reason "Extra input", :pred (clojure.spec.alpha/cat :docstring (clojure.spec.alpha/? clojure.core/string?) :attr-map (clojure.spec.alpha/? clojure.core/map?) :clauses :clojure.core.specs.alpha/ns-clauses), :val
((:require [clojure.string :as s clojure.edn :refer [read-string]])), :via [:clojure.core.specs.alpha/ns-form], :in [1]}], :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0xf80945f "clojure.spec.alpha$regex_spec_impl$reify__2436@f80945f"], :value (
game.lib (:require [clojure.string :as s clojure.edn :refer [read-string]])), :args (game.lib (:require [clojure.string :as s clojure.edn :refer [read-string]]))}, compiling:(game/lib.clj:1:1)
And now we know 😛

hiredman19:10:30

1.10 (which maybe out any month now) may make that error message friendlier too

samcgardner19:10:29

Aaaaaaand the actual code works :rolling_on_the_floor_laughing: Thanks very much for your time and expertise!

dpsutton19:10:51

"1.10.0-beta3" if you want to try out clojure with nicer error messages

samcgardner19:10:02

I very certainly do 😛

dpsutton19:10:06

>>> user=> (load-file "bad.clj") Syntax error macroexpanding clojure.core/ns at (/home/dan/projects/clojure/throwaway/bad.clj:1:1). ((require [clojure.string :as s clojure.edn :refer read-string])) - failed: Extra input spec: :clojure.core.specs.alpha/ns-form

dpsutton19:10:12

not sure if that's better for diagnosis but it's certainly less of an onslaught

samcgardner19:10:11

I don't mind the onslaught so much, more that the pre 1.9 error messages are just java stack traces for a bunch of files I've never seen, which tell me nothing

Alex Miller (Clojure team)19:10:20

^^ fyi, this particular case (which really many of the ns clause failures fall into) is one we’ve been discussing. here there are * clauses, and each one of which is 1 of N options. rather than telling you that it expected one of the N options, it says that it didn’t match anything and found “extra input”, which is true but not helpful. There is a ticket (well, maybe more than one) that fall into this case and it’s a case where we can do better.

Alex Miller (Clojure team)19:10:30

and the error here is require instead of :require

seancorfield19:10:18

(the first error 🙂 )

seancorfield19:10:07

The last few 1.10 builds are much, much better in terms of error reporting -- a big thank you for the work done so far!

👍 4
Alex Miller (Clojure team)19:10:07

I hadn’t read all the backchat

andy.fingerhut19:10:18

It can be difficult to give semantically useful answers to all human-generated errors, as humans are so devious 🙂

Alex Miller (Clojure team)19:10:14

my biggest takeaway from spec’ing ns, is that if we had spec when ns was written, we would have used a different dsl for it b/c it sucks

12
Alex Miller (Clojure team)19:10:35

writing specs for macros often makes that clear very quickly

dpsutton19:10:54

clojure 2.0 metal

seancorfield19:10:05

(ns2 ...) 🙂

dpsutton19:10:16

forgot about that talk 🙂

dpsutton19:10:41

(spacename ...)

😂 4