Fork me on GitHub
#beginners
<
2020-01-24
>
michael.e.loughlin09:01:08

I want to iterate through a value-delimited binary file like it's a seq, is a modified version of line-seq still the best way to go about it?

(defn line-seq
  "Returns the lines of text from rdr as a lazy sequence of strings.
  rdr must implement .BufferedReader."
  {:added "1.0"
   :static true}
  [^.BufferedReader rdr]
  (when-let [line (.readLine rdr)]
    (cons line (lazy-seq (line-seq rdr)))))

andy.fingerhut10:01:29

It certainly sounds like a reasonable way to start, at least. There are certainly tradeoffs that could be made between performance and simplicity of code, but if you are not worried about performance to the Nth degree, I'd start with something simple and measure it to see if it is fast enough for your needs.

mbjarland14:01:58

hi all, given the following kind of pattern:

(-> (sorted-map)
    (parse-first-line first)
    (parse-second-line second)
    (parse-trace trace)
    (parse-locks locks))
I would like to find a way to only call say parse-first-line if first is non-nil and I would like to apply that across all the steps in the threading expression. some-> comes to mind but as far as I can tell does not quite fit this case. Also I could naturally add nil checks to all the parse methods but would like to avoid that duplication. Also considered writing a function which takes the parse function and args as params and I think that wuold work but might affect readability of the above block negatively. What would be an idiomatic way to solve this in clojure?

mbjarland14:01:53

(playing with some code to parse jstack output as a side note)

alexmiller14:01:30

cond-> is good for this general shape, but won't DRY out the repeated nil checks, so could do something over that

mbjarland14:01:26

@alexmiller that feels promising, thank you. Will mull this over.

alexmiller14:01:27

it's kind of a combination of some-> and cond->

mbjarland14:01:46

I think it turns out like this, not bad:

(cond-> (sorted-map)
        first  (parse-first-line first)
        second (parse-second-line second)
        trace  (parse-trace trace)
        locks  (parse-locks locks))

mbjarland14:01:37

and yet again clojure comes up with something beautiful...I think I'm still in love after x years

mbjarland14:01:50

thanks again @alexmiller

parkerjohnsonwebdev14:01:05

hey guys, I believe I'm running into an issue with jdbc inserts where my hyphenated keynames are causing a syntax error - is there a standard way of dealing with this?

parkerjohnsonwebdev14:01:57

(jdbc/insert-multi! spec :test [ {:test-underscore "string"} ]) => ERROR: syntax error at or near "-" (jdbc/insert-multi! spec :test [ {:test_underscore "string"} ]) => ({:id 4, :data nil, :test_underscore "string"})

accounts25915:01:33

I seem to be reaching for the top-down threading macro (`->` ) very frequently -- are there any heuristics y'all use to mediate the use of these kinds of constructs? For example, this barely structured exploration of enlive is just littered with ->; for the most part, when composing a bunch of functions, it feels like the right thing to do https://github.com/qzdl/clj-samplify/blob/master/src/clj_samplify/scraper.clj

matti.uusitalo15:01:21

Looks fine. One sign that you'd want to break out from a threading macro is when you start having to jump hoops to be able to continue in the same thread.

accounts25915:01:20

Thanks for taking the time! I suspect I'm just second guessing myself due to lack of knowledge πŸ™‚ > ...jump hoops to be able to continue in the same thread. I'm not sure I fully understand the above; maybe through positional arguments where -> or ->> are just a pain, or where some branching logic will make the organisation a mess? For anyone with the similar questions, this is a good read https://clojure.org/guides/threading_macros

matti.uusitalo15:01:22

I sometimes have trouble with English idioms. I'll try again. You know how well functions like map, filter and reduce line up inside a ->> macro? Then you need to use that one function where it's not the last argument you want to pass to the function. Like one assoc in there somewhere. Before you know it the beautiful thread is full of anonymous functions and as-> so that you can shoehorn one more function in. That's when you might want to break from one long threading macro to multiple parts.

clojurians97115:01:31

Stuart Sierra wrote an article on the topic a couple of years ago: https://stuartsierra.com/2018/07/06/threading-with-style

chase-lambert15:01:41

I feel like I'm still really naive about error handling in programming. Any good resources you would recommend? I don't seem to stumble upon it when reading clojure stuff. Like just something simple like if a user inputs a string when you need an integer. Try/catch is the thing right? So if an exception is triggered, it "catches" it and you tell your function what to do in that case?

chase-lambert15:01:05

Since such errors would be occurring with side effect-y things like user input and such, is the clojure way to push these things to the boundaries and out of pure functions? Is that maybe why I don't see it too much in the code I read?

ericihli15:01:52

I think you have the right idea in putting those things at a boundary. I found this article and discussion from HN to be an interesting read related to what you're thinking about. https://news.ycombinator.com/item?id=21476261 Sample from the comments. > Another good example of this is having separate classes for something like unsafe strings vs. safe strings in a web app. The functions which interact with the outside world accept unsafe strings and emit safe strings to the rest of the application. Then the rest of the application only works with safe strings. > Anything that accepts a safe string can make an assumption that it doesn't need to do any validation (or "parsing" in the context of the OP), which lets you centralize validation logic. And since you can't turn an unsafe string into a safe string without sending it through the validator, it prevents unsafe strings from leaking into the rest of the app by accident. > This concept can be used for pretty much anything where you are doing data validation or transformation. Also the "functional core, imperative shell" talk by Gary Bernhardt. https://www.destroyallsoftware.com/talks/boundaries

chase-lambert16:01:20

Whoa, I had just bookmarked that Bernhardt talk a couple days ago!

chase-lambert16:01:26

I also tried to read through that Parse, Don't Validate article when it was first published and her followup but it was a bit above my head right now. And I keep getting distracted by the dynamic vs static debate stuff

chase-lambert16:01:02

I'll go back to that. Good to hear I might be on the right track though!

chase-lambert16:01:07

The last time I was trying to do my own hacky error handling I think it was like (if (string? x) do-what-I-want recurse-back-and-ask-for-the-string)

ericihli15:01:14

I think I've seen something where there's a shortcut for using qualified keywords in a map.

^:foo {:bar "baz" :buz "fizz"}
would be equivalent to
{:foo/bar "baz :foo/buz "fizz"}
Is there something like that? I can't find it now.

clojurians97115:01:28

#:person{:first "Han"
         :last "Solo"
         :ship #:ship{:name "Millennium Falcon"
                      :model "YT-1300f light freighter"}}
is read as
{:person/first "Han"
 :person/last "Solo"
 :person/ship {:ship/name "Millennium Falcon"
               :ship/model "YT-1300f light freighter"}}
from https://clojure.org/reference/reader (see "map namespace syntax")

ericihli15:01:24

That's what I was thinking of. Thanks!

christian.paling16:01:41

Hi guys, I have the following code in my application:

(ns tinytimer.db
  (:require [next.jdbc :as jdbc]
            [environ.core :refer [env]]))

(def datasource
  (jdbc/get-datasource (env :database-url)))
I’m using def to create the datasource, since I suppose I should only create it once instead of using defn and create the datasource each time… However with the above code, when compiling he tries to connect to the database.

christian.paling16:01:50

I found on stackoverflow, that I can do something like (when-not *compile-files*) to not evaluate the def during compile time..is that what you would usually do ?

alexmiller16:01:14

don't do that

alexmiller16:01:30

some options: a) don't def this at all - put it in whatever starts your app and pass it to those who need it, either directly or via other component injection means b) use a delay and deref when using

alexmiller16:01:25

I generally try to avoid using def at all for anything stateful

bfabry16:01:21

^ that's definitely one of the things alex is referring to. IMO when you're just starting out I would just do something like this for stuff like that

(defn the-app []
  (let [datasource (jdbc/get-datasource (env :database-url))]
    ...))

bfabry16:01:31

simple is good πŸ™‚

alexmiller16:01:16

yeah, component or integrant etc

bfabry16:01:18

when you find yourself doing that with like 10 different services and they have order dependent initialisations. then I would look into component etc

bfabry16:01:18

when you find yourself doing that with like 10 different services and they have order dependent initialisations. then I would look into component etc

didibus22:01:13

Delay will take care of order of initialization as well

didibus22:01:21

A component like library adds a reverse order stop lifecycle management and sometimes the ability to swap out some parts from the outside

didibus22:01:57

But you'd be surprised how far delays can take you

bfabry22:01:36

as in, they take care of ordered initialisation because they have to be declared in the correct order?

bfabry22:01:32

and clojure does not allow circular namespace dependencies?

didibus03:01:01

Just because they work as a pull model.

didibus03:01:26

Even if you declare them in the wrong order, their body is delayed until something requests it.

didibus03:01:59

So if you want to use X, when you deref X, X will deref the resources it needs and they the one they do, etc.

didibus03:01:39

As a bonus, the compiler will force you to define them in the right order, since otherwise it won't find the symbol.

didibus03:01:48

As @ showed in the other thread, you could use declare if you wanted to define them out of order. I think that would be unidiomatic, but it would still work, since they get initialized as they are realized, not as they are defined.

christian.paling16:01:06

Thank you for the input ! πŸ™‚

didibus22:01:24

@Chris just wrap it in a delay

noisesmith22:01:18

yeah, delays until you run the risk of cyclical delays and then switch to stuartsierra/component or integrant which do proper topo sorting

(ins)user=> (declare a b)
#'user/b
(ins)user=> (def a (delay (cons @b ())))
#'user/a
(ins)user=> (def b (delay @a))
#'user/b
(ins)user=> @a
Execution error (StackOverflowError) at user/fn (REPL:1).
null

bfabry22:01:29

:+1: it's nice that clojure's declare order and no-circular-ns-dependencies requirements at least make this hard

didibus02:01:16

Sure,but lets be real here, that's not going to happen on real use cases

didibus02:01:43

And even if it did, you'd notice instantly and fix it right away

didibus02:01:02

I've been happily using delay for 3 years on multiple systems without a single issue related to them

didibus02:01:40

I think they're undervalued and are quite a simple solution that gets overlooked often in preference of more complicated solutions

didibus03:01:39

For example, in Spring, you get a BeanCurrentlyInCreationException at runtime as it tries and initialize things and detects the circular dependency. While here with delay, if you don't use declare, you get nice compile time error from the Clojure compiler telling you it can't resolve the symbol. And , if you go ahead and add a declare to solve the compiler error, you then get a runtime error StackOverFlow. The only difference here is the error says StackOverFlow intead of BeanCurrentlyInCreationException.

didibus03:01:46

So I'm actually going to argue that, since most often declare is unidiomatic and avoided, delay are even superior to what Spring would do, since you get a compile error, or at least just as good.