beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
Jim Newton 2020-10-26T08:24:33.001200Z

does anyone know whether backquote and ~@ ever produces lazy sequences? or are they always eager sequences?

bronsa 2020-10-26T11:01:00.001800Z

user=> (first `(~@(range)))
0

Jim Newton 2020-10-26T11:10:53.002Z

interesting, good example.

Jim Newton 2020-10-26T11:11:15.002200Z

so at least ~@ respects the laziness of what it is splicing in.

bronsa 2020-10-26T11:12:30.002400Z

yep

bronsa 2020-10-26T11:12:38.002600Z

if you used [~@(range)] it would not terminate

bronsa 2020-10-26T11:12:55.002900Z

you can add a quote in front of syntax-quote to see how it works btw

bronsa 2020-10-26T11:13:13.003100Z

user=> '`(~@(range))
(clojure.core/seq (clojure.core/concat (range)))
user=> '`[~@(range)]
(clojure.core/apply clojure.core/vector (clojure.core/seq (clojure.core/concat (range))))

borkdude 2020-10-26T11:55:38.004300Z

@bronsa It seems tools.reader wraps the thing in another sequence call:

user=> (tr/read-string "'`(~@(range))")
(quote (clojure.core/sequence (clojure.core/seq (clojure.core/concat (range)))))
why is this?

bronsa 2020-10-26T11:57:34.004500Z

because clojure is broken and tools.reader isn't

bronsa 2020-10-26T11:57:36.004700Z

user=> `(~@())
nil

bronsa 2020-10-26T11:57:43.004900Z

tr will return () for that

borkdude 2020-10-26T11:58:05.005100Z

nice. fwiw babashka has adopted this from t.r.

đź‘Ś 1
bronsa 2020-10-26T11:58:22.005300Z

there must be a ticket somewhere in jira

Jim Newton 2020-10-26T12:27:08.008500Z

Has anyone already discussed whether binding should be allowed to return a lazy sequence? To me this sounds dangerous. I'm going to refactor my own code with a macro around binding called something like my-minding which un-lazifies its return value. Otherwise the sequence returned when realised will have code evaluated in a dynamic extend AFTER the extent of the dynamic binding has ended.

Jim Newton 2020-10-26T12:28:33.009400Z

that's not perfect because the return value of binding might be an Record which has a field which is lazy, and that lazy sequence will escape.

2020-10-26T12:30:46.010700Z

It is a semi-common example raised where someone is surprised by this behavior, and then either removes the use of a dynamic variable, and thus binding, or ensures they realize all of the lazy sequence creation inside of the binding that they wish, in the run-time scope of the binding

2020-10-26T12:31:05.011100Z

I doubt there is any plan from Clojure's core team to attempt to catch this situation for you.

Jim Newton 2020-10-26T12:31:41.011500Z

I think it can't be done correctly.

bronsa 2020-10-26T12:31:50.012Z

you can't really prevent binding from returning a lazy-seq

Jim Newton 2020-10-26T12:32:19.013100Z

an application can do it for itself, in that the application knows which scenarios are possible.

Jim Newton 2020-10-26T12:33:25.014300Z

It would be possible to prevent binding from returning a lazy sequence, but it would not be possible to prevent it from returning an object which somewhere within its structure contains a lazy sequence.

bronsa 2020-10-26T12:33:58.014800Z

no, not even the first case would be possible in the general sense

Jim Newton 2020-10-26T12:34:02.015Z

i.e. the macro could simply wrap doall around its body.

Jim Newton 2020-10-26T12:34:18.015600Z

but that would only catch it if the return value is explicitly a lazy sequence.

Jim Newton 2020-10-26T12:34:43.016300Z

@bronsa, can you explain what you mean? which case am I overlooking?

bronsa 2020-10-26T12:34:48.016400Z

right ok, you could force the toplevel lazy seq, not prevent it

bronsa 2020-10-26T12:35:16.016900Z

@jimka.issy we're just talking cross each other, I thought by not allow you meant disallow

Jim Newton 2020-10-26T12:35:46.017800Z

ahhh.. ok, but anyway, you can't prevent a sequence of sequence of sequence of sequence of Records which a field which is a lazy sequence.

bronsa 2020-10-26T12:36:10.018500Z

going down that route you could walk the return value forcing all lazy seqs

bronsa 2020-10-26T12:36:16.019Z

but not a good idea :)

2020-10-26T12:36:25.019300Z

Having a compile time check that caused the Clojure compiler to reject code is the situation that I think bronsa thought Jim meant. That is computationally undecidable.

đź‘Ť 1
Jim Newton 2020-10-26T12:36:38.019700Z

right, not a good idea. except perhaps on an application by application basis.

Jim Newton 2020-10-26T12:42:20.021800Z

@andy.fingerhut there is a similar problem with lazy sequences and exceptions, in particular reduce/reduced ... I ran into this in Scala some months ago, where if the escape-from-reduce instruction is called lazily. long after the lazy sequence has been returned from its generating function, the exception will be thrown outside the try/catch. Same principle, try behaves like a dynamic variable binding.

2020-10-26T12:43:34.022900Z

Sure, the more general rule is "mixing constructs that rely on dynamic scope and lazy sequences is dangerous", where both binding and try are examples of constructs that rely on dynamic scope

1
2020-10-26T12:44:23.023500Z

Clojure does not have a ton of constructs that introduce dynamic scope, but there are a few.

Jim Newton 2020-10-26T12:45:43.024200Z

what happens when I walk through a lazy sequence? does that guarantee to unlazify it? Or do I really need to copy it? I'm trying to think about how to write recursive-doall

bronsa 2020-10-26T12:49:55.024800Z

no need to copy

Jim Newton 2020-10-26T12:50:37.025500Z

it is a bit unsettling that in a function language looking at data changes it. But I understand the tradeoff of speed for correctness.

bronsa 2020-10-26T12:52:48.025900Z

it's not really about speed vs correctness

Jim Newton 2020-10-26T12:52:55.026200Z

oh?

bronsa 2020-10-26T12:53:12.026600Z

it's the semantics of a lazy collection in an eagerly evaluated language (+ constructs that unwind stack)

Jim Newton 2020-10-26T12:54:21.027900Z

explain more.. it seems to me that the lazy collection object changes destructively when I look at it. I.e. a function which looks at it will get a different result the second time it looks.

bronsa 2020-10-26T12:54:49.028800Z

no that's not the case, a lazy collection doesn't change its value, it just caches computation

Jim Newton 2020-10-26T12:55:13.029900Z

isn't it the case that rest called on a lazy sequence will return an object of a different type before and after second is called ?

2020-10-26T12:55:18.030100Z

the JVM objects change in place, that is true, but saying you get "a different result" raises the question of what you mean by "different". The result should be = according to clojure.core/=, even though JVM objects are being mutated.

bronsa 2020-10-26T12:56:33.031200Z

@jimka.issy in some cases, yes, but that's because you shouldn't care about the type of lazy collections, clojure makes no promise at all about the concrete type of a value

bronsa 2020-10-26T12:56:38.031500Z

that's an implementation detail

bronsa 2020-10-26T12:56:55.032100Z

yes, internally there is mutability

bronsa 2020-10-26T12:57:03.032600Z

but the interface is referentially transparent

Jim Newton 2020-10-26T12:57:20.033300Z

yes but if you're writing a program which manipulates types, then you do care about types.

2020-10-26T12:57:44.034100Z

As I mentioned before, Clojure sequences are not linked lists of cons cells (except when you explicitly create them that way, but most functions don't). Using identical? and expecting it to return true when a linked list of cons cells would is likely to bite you when you least expect it.

bronsa 2020-10-26T12:57:54.034400Z

if you're writing a program which manipulates concrete types of clojure collections, you're usually doing something you shouldn't be doing

bronsa 2020-10-26T12:58:33.034900Z

clojure doesn't tell you which type it will return from lazy-seq, that's by design

bronsa 2020-10-26T12:58:45.035300Z

even map literals may have different concrete types depending on how many values they hold

bronsa 2020-10-26T12:59:22.036Z

but for sequences, the only thing you should care about is that they're sequences, not what internal type of sequences they are

âž• 1
Jim Newton 2020-10-26T12:59:44.036900Z

I'm not convinced about the part of "shouldn't be doing". I'm still of the mind that it is something to be understood rather than to be avoided.

Jim Newton 2020-10-28T09:46:47.226Z

@bronsa, advice about good programming style is much appreciated. However, the problem I'm working is not really about choosing the best way to represent sequences, but rather static program analysis. Given a program, from a client/user, examine it and draw conclusions about its correctness. If I have concluded that a certain object is both an A and also a B, I need to ask the question whether the set of A's and the set of B's have a non-void intersection. If not, this means there is either an error in the program, or there is unreachable code. If I can prove that the intersection of A and B is void, ie. A and B are guaranteed to be disjoint, then I can draw an interesting conclusion about the code, or I can optimize the code in an interesting way. Granted, the most interesting theoretical problems are the corner cases of corner cases, which may never really occur in practice. Nevertheless, they do occur often in randomly generated tests. And passing the randomly generated tests improves the code, and gives more confidence of its correctness.

bronsa 2020-10-26T12:59:49.037100Z

it could, and has, changed between clojure versions

bronsa 2020-10-26T13:00:18.037900Z

e.g. atm (range) returns an Iterate

bronsa 2020-10-26T13:00:22.038300Z

it used to return a LazySeq

bronsa 2020-10-26T13:00:30.038700Z

but you shouldn't care about it.

2020-10-26T13:00:35.038900Z

It seems like it might be reasonable to want to type things as "some kind of sequence" or "some kind of map". It is getting more fine-grained than that, that will cause frustration, I believe.

ghadi 2020-10-26T13:01:01.039600Z

Program to the abstraction, not the concretion(s)

bronsa 2020-10-26T13:02:28.039800Z

sure it can be useful to understand the internal details

bronsa 2020-10-26T13:02:33.040Z

but that's all they are, implementation details

bronsa 2020-10-26T13:02:45.040200Z

if you depend on implementation details in any language, that'll bite you back at some point

bronsa 2020-10-26T13:03:10.040400Z

of course there are specific instances where you do want to do that

bronsa 2020-10-26T13:03:38.040600Z

but those are very few and special instances that should be considered just that, special

Jim Newton 2020-10-26T13:03:47.040800Z

but these internal details can make for really different semantics of the program. And also you can depend on the details without knowing it. How can you figure out whether a program is depending on details of implementation. I think that's probably an unsolvable problem.

bronsa 2020-10-26T13:07:30.041Z

but these internal details can make for really different semantics of the program I'd say that's only the case if you depend on behavior that's not promised by the interface

bronsa 2020-10-26T13:07:51.041200Z

How can you figure out whether a program is depending on details of implementation that's the job of the documentation

bronsa 2020-10-26T13:08:20.041400Z

a silly example: laziness in clojure is best effort, not guaranteed to be 1 item at a time

2020-10-26T13:08:48.041700Z

I think it is fair to say that Clojure's official documentation is terse enough in some cases that some people "fill in the blanks" differently than the author intended.

bronsa 2020-10-26T13:08:55.041900Z

fair

2020-10-26T13:09:19.042100Z

But I also believe that would sometimes happen even if the documentation were 10x longer.

2020-10-26T13:10:14.042300Z

It is difficult to determine whether someone's mental model of how something actually works, is a good enough fit for how it works.

Jim Newton 2020-10-26T13:11:11.042500Z

@bronsa, not sure if you disagree or don't understand my claim. Given a program, I think there's no way to tell whether the program depends on some undocumented feature of the language or upon some implementation detail. my gut feel is this is related to the halting problem. Even if the user reads all the documentation, there's no way to know whether the program misapplied the rules and and still just accidentally works.

bronsa 2020-10-26T13:11:54.042700Z

Given a program, I think there's no way to tell whether the program depends on some undocumented feature of the language or upon some implementation detail
programmatically, yeah sure

bronsa 2020-10-26T13:12:04.042900Z

but that's true for (almost) every language

Jim Newton 2020-10-26T13:14:46.043200Z

agreed. functional languages, or even function style in an imperative language make some problems less likely. For example the use of non-mutable data structures eliminates some problems. But in clojure lazy sequences are mutable. They're not mutable from the sense of = but they are mutable from other lenses.

bronsa 2020-10-26T13:15:07.043400Z

I work on a functional, typed language that can formally verify itself, and even there you can still express "correct" programs that depend on implementation details

Jim Newton 2020-10-26T13:15:33.043600Z

To me, it is something that I just need to understand, and avoid depending on accidental features.

bronsa 2020-10-26T13:15:54.043800Z

saying "in clojure lazy sequences are mutable" is wrong

bronsa 2020-10-26T13:16:02.044Z

they use mutability in their impl

bronsa 2020-10-26T13:16:04.044200Z

they're not mutable

bronsa 2020-10-26T13:16:21.044400Z

i.e. there is no API that allows you to mutate a lazy sequence in place

bronsa 2020-10-26T13:16:35.044600Z

it's an important distinction

Jim Newton 2020-10-26T13:16:46.044800Z

doesn't second mutate a lazy sequence?

bronsa 2020-10-26T13:16:46.045Z

even in haskell some purely functional collections can be implemented with mutability

bronsa 2020-10-26T13:17:06.045200Z

no, I see what you mean but no

bronsa 2020-10-26T13:17:12.045400Z

second realizes the lazy seq

bronsa 2020-10-26T13:17:29.045600Z

internally that does mutation to cache the return value

bronsa 2020-10-26T13:17:45.045800Z

but that doesn't make a lazy-seq mutable

bronsa 2020-10-26T13:18:08.046Z

there's an important distinction between the interface of a collection and its implementation

bronsa 2020-10-26T13:18:21.046200Z

as long as the interface is immutable, the collection is effectively immutable

bronsa 2020-10-26T13:18:57.046400Z

otherwise nothing is really immutable

bronsa 2020-10-26T13:19:17.046600Z

everything mutates some segment of memory in the end

2020-10-26T13:19:31.046800Z

A Haskell implementation (even if you remove the unsafe parts) is doing mutation all over the place in its implementation, by necessity.

2020-10-26T13:20:02.047Z

Well, at least by necessity of all practical computers implemented today.

Jim Newton 2020-10-26T13:20:33.047200Z

So I don't know Haskell, but. in Haskel does the type of an object change when you examine it? or does its type stay the same over its lifetime?

2020-10-26T13:21:52.047500Z

AFAIK, the type stays the same during its lifetime. In the JVM, the type of an object also remains the same over its lifetime. Are you thinking that lazy sequences in Clojure somehow cause the concrete type of an object to change during its lifetime? If so, what scenario is that?

2020-10-26T13:24:38.047700Z

There are many situations in Clojure where unless you know implementation details, you cannot easily predict across different versions of Clojure what the concrete JVM type of an object will be returned from a function like range, for example, but for a single version of Clojure, knowing the implementation details, you can predict the concrete types. But Clojure doesn't promise concrete JVM types. It promises "some object that will implement this interface" (at least in most cases).

âž• 1
bronsa 2020-10-26T13:25:29.048Z

clojure doesn't "change the type of an object when you examine it" either

Jim Newton 2020-10-26T13:25:31.048200Z

I guess I don't know enough about the situation to come up with an example.

Jim Newton 2020-10-26T13:26:59.048400Z

I was under the impression that the following calls to type would return different values the first and second. time. But @bronsa is absolutely correct that type returns the same thing both times.

(let [obj (map (constantly 12) (concat '(1) '(2 3 4)))]
  [(type (rest obj))
   (second obj)
   (type (rest obj))])

bronsa 2020-10-26T13:27:37.048600Z

it's not theoretically impossible for it not to

bronsa 2020-10-26T13:27:50.048800Z

but if it did, it would be an impl detail :)

bronsa 2020-10-26T13:28:18.049Z

you shouldn't even know what concrete type that returns

bronsa 2020-10-26T13:28:41.049200Z

there are so many concrete seq types in clojure, the important thing is that they are all ISeq

bronsa 2020-10-26T13:29:14.049400Z

(i.e. (seq? x) is true for all of them)

2020-10-26T13:31:08.049800Z

My head hurts trying to understand "it's not theoretically impossible for it not to" 🙂

bronsa 2020-10-26T13:31:57.050Z

I can write a deftype that is a seq where the (type (rest x)) returns different values before/after (second obj)

bronsa 2020-10-26T13:32:07.050200Z

I can't think of an existing clojure sequence that does that

bronsa 2020-10-26T13:32:15.050400Z

but it's possible to make one

bronsa 2020-10-26T13:32:21.050600Z

and it would still be a correct impl

2020-10-26T13:33:27.050800Z

That my head can handle more easily 🙂. But even if you made a situation where (type (rest x)) return a different type before and after a call to second, it would not be because JVM objects were changing type under the hood, but because (rest x) was returning a reference to a different JVM object on the two different calls. (that is implementation detail, yes)

bronsa 2020-10-26T13:33:46.051Z

yep

2020-10-26T13:35:56.051200Z

If any other Clojure beginners have read this far, please know that we are definitely straying out of materials most beginners would ever need/want to know.

❤️ 1
2020-10-26T13:39:35.051400Z

Jim, if you want to think of a type system relevant for Clojure programmers, that tries to avoid implementation details about particular JVM concrete object types when Clojure doesn't promise them, then something like "The function rest returns an object x such that (seq? x) is true" is far more useful than "The function rest returns an object with class X, or Y, or Z, or W, ..."

Jim Newton 2020-10-26T13:44:59.051600Z

yes, but computationally seq? is the same as (fn [x] (instance? clojure.lang.ISeq x))

2020-10-26T13:45:08.051800Z

It does not happen very often, but that list of JVM classes for which (seq? x) returns true often gets larger when someone implements a new Clojure collection type. It is common to create a custom seq type that is expected to be more efficient when creating a sequence of elements of that collection.

2020-10-26T13:45:40.052Z

agreed on what seq? is equivalent to, and that is an implementation detail that would shock me to my core if it ever changed, so I think you can rely on that.

bronsa 2020-10-26T13:46:26.052200Z

seq? will always return true for sequences

2020-10-26T13:46:31.052400Z

The main point is that clojure.lang.ISeq is a Java interface, not a Java class, and an open-ended set of Java classes can implement that interface in the future.

bronsa 2020-10-26T13:46:39.052600Z

(seq? x) = true is what makes x a sequence :)

bronsa 2020-10-26T13:47:18.052800Z

note that ISeq is not a concrete class, it's an interface/abstraction

bronsa 2020-10-26T13:47:39.053400Z

which comes round to what I was saying earlier, clojure makes promises on abstractions, not concrete types

bronsa 2020-10-26T13:49:22.055100Z

and testing for ISeq instead of using seq? is ok, since ISeq is part of the public API, not an impl detail

mashimom 2020-10-26T13:50:17.055800Z

Hi, how do I go from a seq of values to a seq of partial functions?

(range 0 16 4)
(defn my-fn [n other] ...)
'((partial my-fn 0) (partial my-fn 4)...)

bronsa 2020-10-26T13:51:19.056200Z

map

bronsa 2020-10-26T13:52:21.056500Z

(map (fn [x] (partial my-fn x)) (range 0 16 4))

đź‘Ť 1
Ryan Moore 2020-10-26T19:00:52.064400Z

Hey everybody, I’m having a little trouble with a custom generator for a spec …see thread. (I figured I would post here since it feels like a beginner question.)

Ryan Moore 2020-10-26T19:01:23.064500Z

So here is a little toy example that I’m working on for learning purposes.

(s/def ::x ::nice-number)
(s/def ::y ::nice-number)
(s/def ::point (s/keys :req [::x ::y]))
(s/def ::monotonically-increasing-x
  (s/and (s/coll-of ::point
                    :distinct true
                    :min-count 2)
         ;; x values should be monotonically increasing
         #(apply < (map ::x %))))

Ryan Moore 2020-10-26T19:01:51.064700Z

The problem is generating examples like with exercise because of the monotonic increasing requirement

Ryan Moore 2020-10-26T19:02:06.064900Z

I can get a set of increasing numbers like this:

(def sorted-x-gen
  (gen/fmap #(vec (sort %))
            (gen/such-that not-empty
                           (gen/set (s/gen ::x)))))

Ryan Moore 2020-10-26T19:02:43.065100Z

But I’m having trouble actually going from that to an actual coll of monotonically-increasing-x. Any suggestions?

2020-10-26T19:15:58.065300Z

I believe if you do a different function than vec on the return value of (sort %), e.g. (map point-from-x-value (sort %)), where point-from-x-value is a function you write that takes an x value and creates a value that satisfies the ::point spec from it, perhaps by combining it with a randomly generated y value, that might be what you want.

Ryan Moore 2020-10-26T19:43:18.065500Z

Yeah I could generate the x and the y at the same time something like this….and then just fill in some-fn

(gen/fmap some-fn
            (gen/such-that not-empty
                           (gen/set (s/gen (s/cat :x ::x :y ::y)))))

Ryan Moore 2020-10-26T19:43:44.065700Z

I could also try gen/map to generate a map directly and then sort that….will tinker around with both options, thanks

2020-10-26T20:32:26.066900Z

I am pretty sure that this kind of use is exactly what fmap was intended for -- calculate some data with whatever restrictions you find useful, and then transform it into something else that satisfies your spec.

đź‘Ť 1
2020-10-26T23:52:58.070Z

Hey team, noob ring question:

(compojure.core/routes #'routes/app ...)
https://github.com/PrecursorApp/precursor/blob/master/src/pc/server.clj#L87-L90 I see that in order to make hot reloading work, we pass vars in as the handlers. In my old project, I didn’t quite do it that way. Instead, I made sure that I was passing vars as the fn definition: https://github.com/stopachka/jt/blob/master/server/jt/core.clj#L644
(POST "/emails" [] emails-handler) ; emails-handler i guess gets captured as a var
Is my intuition correct than, that had I written:
(compojure.core/routes #'routes/app ...)
Than, I wouldn’t need to wrote about hot reloading in per route? So I could write:
(POST "/emails" [] (fn [req] ...) ; emails-handler as anon fn

2020-10-26T23:53:11.070500Z

Is there’s a good writeup that goes into this var business + ring, would love to read it!