Fork me on GitHub
#beginners
<
2023-10-14
>
Sahil Dhanju17:10:43

Is it possible to have a symbol (i.e. a def) that always gets re-evaluated when evaluated? Basically I want a def that acts like a function but I don't need to invoke it

Matthew Twomey17:10:39

I don’t think that is possible using a def. What are you trying to do here?

Matthew Twomey17:10:51

(for example, are you trying to mirror the implicit “getter” sort of functionality that some languages have where you treat a variable like a property but it’s actually evaluating a function behind the scenes?)

pppaul17:10:39

this sounds like the default behavior. could you give a workflow or code example

Matthew Twomey17:10:34

I think he wants something like this:

(def time (new java.util.Date))
Where each time you refer to time it will be re-evaluated.

pppaul17:10:39

oh, def syntax but fn behavior. hmmm

Matthew Twomey17:10:23

Yeah, several other languages have this sort of syntactic sugar that allows you to “fake treat” a function or method as though it were a symbol or variable or property. It’s something I missed a little when I started using Clojure but only for a short while.

pppaul17:10:50

maybe can be done with a macro, but this is breaking the tiny amount of syntax that lisp has

Matthew Twomey17:10:05

yeah, I think the desire for it goes away pretty quick

pppaul17:10:24

I imagine this feature would break other macros like threading, basically calling these type of functions 2 times

Matthew Twomey17:10:27

I think the closest you can get to that without things getting squirrelly is (def time (fn [] (new java.util.Date))) then just referring to time as (time) from there on out.

Sahil Dhanju17:10:30

Yes it's more of a nicety - the example here is I have a def which is referring to the contents of a file I've read. During development it would be nice if whenever that def is used the file was read from disk every time but then during a real run of the application this doesn't happen

Sahil Dhanju17:10:11

And what's nice is the code using the def doesn't need to care about this detail

Sahil Dhanju17:10:10

The solution you gave specifically is what I didn't want by changing time -> (time) but oh well that's what I'm doing now 😄

pppaul17:10:44

why does the syntax matter, why can't you use fn call syntax and dispatch in the function for your desired behavior

Sahil Dhanju17:10:07

Because then the calling code has to understand this is something to invoke rather than use

Sahil Dhanju17:10:33

It's very very minor, just was wondering if it's possible

pppaul17:10:26

but you do want to invoke this. you are describing invocation.

Sahil Dhanju17:10:39

I wanted to make invocation an implementation detail

pppaul17:10:58

you are creating an ambiguous syntax, and it sounds like you want to make a new language

Matthew Twomey17:10:49

are you concerned with re-evaluating the def constantly once in production?

Sahil Dhanju17:10:02

It's like what Matthew said, other languages have this feature and it's something you can get a lot of mileage out of

Sahil Dhanju17:10:44

Yes in production this shouldn't be evaluated again and again, it really should be a symbol

Matthew Twomey17:10:03

You could do this to avoid that, but I’m not sure it’s any more palatable:

(def dev true)

(def _time (new java.util.Date))

(def time (fn [] (if dev (new java.util.Date) _time)))

Matthew Twomey17:10:17

That way it’s eval’d once in prod and every time in dev.

Sahil Dhanju17:10:37

Yes was thinking the same thing - still a function call but oh well

Sahil Dhanju17:10:40

Thanks for the answers!

Matthew Twomey17:10:59

sure. I hear you, I missed this too.

Matthew Twomey17:10:30

I think it just doesn’t really fit with a functional style and that’s probably why it’s not a part of clojure.

pppaul17:10:17

I don't see this as functional vs something else. it really looks like a fundamental syntax issue

pppaul17:10:58

my programs have this behavior, it's easy to do this type of dispatch, but I don't make up new syntax for it

Matthew Twomey17:10:04

You typically see it in oo languages as a “magic” getter on an object property.

pppaul18:10:06

I don't see this ending well. what does your magic symbol do when (my-sym) is done

pppaul18:10:35

that js behavior sounds like a cache lookup

pppaul18:10:42

clojure has this

Matthew Twomey18:10:07

(but it doesn’t have the syntatic sugar around it, which is fine)

pppaul18:10:12

the syntax is invocation

Matthew Twomey18:10:16

In the js example I linked, without this you’d need to do console.log(obj.latest()); - note the () after latest.

pppaul18:10:44

I understand that you don't want the invoke syntax, but that's ambiguous

Matthew Twomey18:10:08

Which is probably why it’s not a part of clojure.

pppaul18:10:54

embrace the invoke. it's a type of documentation. however at startup you do have the ability to do some hacky things (config like things) but I think if you are doing invoke stuff, then the hacky things to not do the invoke will bite you in the bum later

Matthew Twomey18:10:16

(please remember, I’m not the one who was asking)

Matthew Twomey18:10:32

I’m just explaining why someone might want this, because I went through this too.

pppaul18:10:38

yeah, I understand. I am not talking specifically to you when I write. otherwise I would have just ended with it's ambiguous syntax

👍 1
seancorfield18:10:47

@U05N8AJMWQG Would you be satisfied with a solution where you use @data instead of data? If so, you could do something like this:

user=> (def now (reify clojure.lang.IDeref (deref [this] (java.util.Date.))))
#'user/now
user=> @now
#inst "2023-10-14T18:14:57.928-00:00"
user=> @now
#inst "2023-10-14T18:15:01.017-00:00"
user=> @now
#inst "2023-10-14T18:15:04.936-00:00"
user=>

2
Sahil Dhanju21:10:28

Oh wow interesting... definitely a bit magical but would satisfy this. I'll play around with that, thanks!

Jason Bullers04:10:37

What exactly do you mean by "everytime it's read in development"? Like in a bunch of calls, you see different file contents? Or more like you adjust some stuff and want to re-run the code against the latest file state? If it's the latter, does some sort of reloaded workflow help here, where you reload the namespace rather than evaluating each individual form (I learned about this recently). In that case, every reload would rebind the def to the latest value. And you don't have to worry about prod since you're not reloading namespaces in prod. So you'd change your file, reload the ns, try out your functions against the current file. Rinse and repeat.

Jason Bullers04:10:41

I was thinking of the deref thing @U04V70XH6 showed above, I just had no idea how to do it (still learning). Really neat how easy it is to create a custom deref-able. I guess the downside is having this weird deref in prod too. Seems like something that might be confusing later or to someone else.

Sahil Dhanju12:10:08

Yes that's exactly what I mean - what I was wanting is the minimum amount of steps between changing the file and rerunning the code - which hopefully is 0 steps. If there is a step it may be possible to forget that step and then see incorrect behavior and potentially start debugging why the behavior is incorrect because I forgot I need to reload the file. That's happened to me many times on other projects

Jason Bullers12:10:56

Do you have any habits already, like saving the file regularly? You can probably bind reloading to saving, and then you won't forget. I know what you mean though: I've been confused a few times for the same reason. I'm building the habit of reloading though. It becomes muscle memory pretty quickly.

Sahil Dhanju18:10:22

I don't believe in infallible habits 😄 But I understand what you mean

Jason Bullers17:10:23

Is there a nice idiom for getting the nth element of a collection during a set of sequence operations? Say I want to iterate a function 50 times and then perform a reduction on the result. Something like:

(->> input
     (parse)
     (iterate f)
     (nth 50)
     (reduce g))
This doesn't work since the arg order for nth is "backwards". I could do a drop followed by first, but that seems a bit clunky.

daveliepmann17:10:27

Preferences and contexts vary. In a case like this I might put the nth and reduce outside the thread, to make clear what are sequence ops and what are non-sequence ops. (I find this especially nice for "do this to the result" situations.) If you really prefer to stick to threading instead of nesting there's https://clojuredocs.org/clojure.core/as-%3E> or multiple techniques under "Don't" in https://stuartsierra.com/2018/07/06/threading-with-style.

👍 1
Jason Bullers18:10:13

Right, but then I need to name the aggregate of states which is kind of meaningless since I only care about the final state. A clearer question maybe: is there any idiom for "I want to iterate a function n times and only care about the nth result"?

Bob B18:10:48

I don't know that there's a common idiom for that specific thing, but one possibility for this specific use case is

(let [flip-nth #(nth %2 %1)]
  (->> "abcdef"
    seq
    (flip-nth 3)))
and of course, if this comes up a lot in a particular place, flip-nth could be elevated up to a top-level form for re-use, or a function could be made that comps nth and iterate to provide a local idiom for that specific thing

💡 1
1
daveliepmann18:10:25

My mistake, I thought you were focused on the threading aspect. I think I understand what you're asking now. For "I want to iterate a function n times and only care about the nth result" I consider "`drop` followed by first" a very good solution. I don't find it clunky at all — it seems like a clear representation of your intent.

👍 1
pppaul19:10:50

the assumption is that iteration produces a list that has at least as many els as the iteration amount. I feel like there are too many assumptions here for something like an idiom to form. also iterate isn't too popular of a function

dpsutton20:10:54

I’d write a helper function that does exactly what you want and then invoke it. It could use a loop construct or some other profiled form to do what you want. The details aren’t important and forcing it into a threading form is pushing you towards versions that you find clunky and wasteful

👍 2
Jason Bullers01:10:57

Right, that's what I ended up doing. I find it easy to forget to do that when I'm playing around with the repl; it's really easy to just keep growing a giant thread macro until the whole problem is solved. In my case, the iteration was because I was simulating something, so I lifted the iterate and nth out to a simulate function (easy to switch to drop first or loop if needed, as was suggested):

(->> input
     (parse)
     (simulate f 50)
     (reduce g))
Thanks all!

👍 2