clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
2021-01-08T05:28:32.361200Z

How do you unbind a binding? Is that even possible? Say I want the var deref to fetch the root again, but I'm inside a (binding [] ...) expression?

euccastro 2021-01-08T06:04:21.362100Z

@didibus maybe there's a better way, but (alter-var-root #'*foo* identity) works

dpsutton 2021-01-08T10:20:23.368500Z

The end of the binding context will have that pop thread bindings.

2021-01-08T17:24:39.374800Z

right, and popping twice, or failing to pop, will break things

2021-01-08T18:26:25.376900Z

Ya, but also, if you want to remove a binding which isn't the last, you can't because it's a stack, so I wouldn't think there'd be a function for it

2021-01-08T18:27:16.377100Z

Though it makes me curious about the lookup process, I would have expected having a map of stacks or something, now it seems you have to search the stack for the newest instance.kf the specific var

2021-01-08T18:42:10.377900Z

it's a stack of maps - each layer is a hash map of shadowings of the ones below it

2021-01-08T18:49:41.378100Z

Doesn't that mean it needs to traverse through the whole stack? Imagine:

(def ^:dynamic foo 1)

(binding [foo 2]
  (binding [other :bar]
    (binding [other :baz]
      foo)))

2021-01-08T18:50:52.378400Z

And I'm guessing to find that there are no binding it also must traverse through it all:

(def ^:dynamic foo 1)

(binding [other :bla]
  (binding [other :bar]
    (binding [other :baz]
      foo)))

2021-01-08T18:52:08.378600Z

that's what it seems like, yeah

2021-01-08T18:52:44.378800Z

And the structure would be like:

[{:other :baz} {:other :bar} {'other :bla} {'foo 1}]

2021-01-08T18:53:03.379Z

That might explain why dynamic vars are slower than ThreadLocal

2021-01-08T18:53:07.379200Z

well vectors as stacks go in the opposite direction, but yeah

2021-01-08T18:53:28.379400Z

AH lol, I wasn't sure and actually typed it the other then flipped it πŸ˜›

2021-01-08T18:54:52.379600Z

I would have expected it to be:

{'foo [1 2]
 'other [:bla :bar :baz]}

2021-01-08T06:06:21.362400Z

Good idea

2021-01-08T06:10:10.362600Z

That also made me think to look at the implementation, seems you can also do:

(.getRawRoot #'my-var)

πŸ‘ 1
euccastro 2021-01-08T06:13:35.362900Z

nice, thanks!

2021-01-08T07:37:39.363300Z

alter-var-root doesn't change the binding inside a binding context though

2021-01-08T07:39:12.363500Z

oh - but it returns the identity, I see

2021-01-08T07:39:46.363700Z

it doesn't do the thing literally asked for - unbinding

2021-01-08T07:41:45.363900Z

think I said "or get the root binding"

2021-01-08T07:42:00.364100Z

Anyways, I just wanted a way to get the root value even in there is a thread binding

2021-01-08T07:42:21.364300Z

aha

2021-01-08T07:42:44.364500Z

I guess there is popThreadBinding too or something like that... not sure if that would unbind?

2021-01-08T07:43:27.364700Z

Except with that, you can't really choose which binding to remove it seems

2021-01-08T07:44:28.364900Z

that just crashes the repl if you didn't have a matching push-thread-bindings

2021-01-08T07:44:40.365100Z

it's low level and fragile

2021-01-08T07:45:54.365300Z

in fact binding is just a macro around push-thread-bindings / pop-thread-bindings

2021-01-08T07:49:38.365500Z

If its a stack though, there's probably no unbind function

2021-01-08T08:45:55.367300Z

(conj)
;=> []
(conj nil)
;=> nil
(conj nil 1)
;=> (1)
Kinda weird how inconsistent conj is here no?

vlaaad 2021-01-08T08:59:41.367500Z

Knowing about 0 and 1 arities involvement in reducers/transducers, all I see is consistency, damn curse of knowledge!

2021-01-08T10:21:03.368700Z

How to tell you've written too much clojure? The above behaviour makes perfect sense to me

caumond 2021-01-08T10:55:33.368900Z

Could you comment the first one, I don't get it

caumond 2021-01-08T10:55:48.369100Z

why is it consistant to return an empty vector?

2021-01-08T11:25:55.369300Z

0 arity is often used to produce an initial value

πŸ‘ 1
βœ… 2
2021-01-08T13:01:09.370200Z

why (conj) doesn’t produce () ? since (conj nil 1) gives (1). Also, why (conj nil) gives nil ?

vlaaad 2021-01-08T13:16:02.370400Z

1-arity is completing arity for transducers, e.g. something that does final post-processing. Usually it behaves as identity, but some transducers like partition-all can add accumulated elements to a final result.

vlaaad 2021-01-08T13:16:54.370600Z

(conj) returns vector instead of list because vectors are more useful and performant data structure than linked lists

vlaaad 2021-01-08T13:17:16.370900Z

why should it return ()?

vlaaad 2021-01-08T13:17:53.371100Z

(conj [] 1) is [1] , not (1) after all

alexmiller 2021-01-08T15:15:24.373100Z

hello all, just a reminder that clojure 1.10.2-rc2 was released this week - would love to have you try it and report back here if things look good, or if you see any issues. changelog can be found here: https://github.com/clojure/clojure/blob/master/changes.md#changes-to-clojure-in-version-1102. If you do try it please hit the check mark emoji here!

βœ… 8
dharrigan 2021-01-11T14:08:59.064300Z

been running 1.10.2-rc2 for the past few days on 3 backend services. all working normally πŸ™‚ nothing weird observed πŸ™‚

πŸ‘ 1
2021-01-08T17:06:55.374400Z

I find it inconsistent that (conj) and (conj nil 1) don't both create a vector or a list personally

2021-01-08T17:07:13.374600Z

And preferably it be vector for both, since like you said, more useful

seancorfield 2021-01-08T18:25:25.376800Z

We have it running in QA (as of yesterday afternoon) and everything looks good so far. I expect we'll put it in production early next week (we're mostly on Alpha 4 in production right now, with one app running RC 1).

2021-01-08T19:41:30.387900Z

Running into an interesting issue with a deps.edn based project. I have often used Java's Thread/setDefaultUncaughtExceptionHandler in Clojure projects. Its use seems to require that the caller be a first class java object

2021-01-08T19:41:55.388400Z

which is easily obtained through ns and :gen-class

Derek Passen 2021-01-08T19:42:59.389700Z

See https://stuartsierra.com/2015/05/27/clojure-uncaught-exceptions for how to use reify with that function

2021-01-08T19:43:35.390100Z

Nice, that may make the tooling easier

Derek Passen 2021-01-08T19:43:46.390400Z

Hope so

2021-01-08T19:43:58.390900Z

all clojure values other than nil are first class java objects - I think what you mean here is that it needs to implement a specific interface?

dpsutton 2021-01-08T19:46:27.392200Z

(Thread/setDefaultUncaughtExceptionHandler
  (reify Thread$UncaughtExceptionHandler
    (uncaughtException [_ thread ex]
      (prn "ouch i died in a thread"))))
nil
user=> (.start (Thread. (fn [] (/ 1 0))))
nil
user=> "ouch i died in a thread"

2021-01-08T19:46:33.392400Z

Ya you are right Justin. I am not sure what the dependency is.

2021-01-08T19:46:40.392600Z

(to me the distinction would be eg. Scala where many constructs that exist as far as the language is concerned don't exist in a first class way in the vm - clojure intentionally doesn't do that)

2021-01-08T19:48:46.394Z

In a clojure cli deps.edn project, I am not able to successfully register an exception handler without AOT compiling the class that invokes it

clojure -e "(compile 'indignant.core)"
java -cp $(clojure -Spath):classes indignant.core

2021-01-08T19:49:57.394300Z

Oops, no I am using reify already. I even work at reify πŸ˜€

2021-01-08T19:50:12.394500Z

(defn set-exception-handler
  []
  (Thread/setDefaultUncaughtExceptionHandler
   (reify Thread$UncaughtExceptionHandler
     (uncaughtException [_ thread e]
       (do
         (timbre/errorf "top level exception handler caught exception on thread: %s" (.getName thread))
         (timbre/error e)

2021-01-08T19:51:26.394900Z

Trying to understand what might cause that requirement

2021-01-08T19:52:07.395Z

Re 0-arity. Look at (+) or (*). They return a 'reasonable' initial value to be used for repeated application of the same function. You agree vector is fine. Why does (+) return a long, when (+ 0M) returns a BigDecimal? Many of the 1-arity functions are essentially identity. Note that (+ nil) returns nil even though (+ nil 1) will error. Ditto with (conj :foo) vs (conj :foo 1) So why doesn't (conj nil :x) promote a nil to a vector? I'm not aware of any other fns where nil ever becomes anything other than (). So nil => () makes sense, as it's consistent with all other functions in core where nil can be used in place of a collection.

borkdude 2021-01-08T19:52:46.395500Z

Make a minimal repro, else it's hard for anyone to help you probably

πŸ‘ 2
2021-01-08T19:53:03.395800Z

indeed thanks

2021-01-08T19:53:24.396100Z

might find the problem myself that way

2021-01-08T19:54:16.396800Z

well that java -cp .. line, since it ends in indignant.core only works if you have created indignant.core as a class java can find

2021-01-08T19:54:43.397300Z

have you tried java -cp $(clojure -Spath) clojure.main -m indignant.core ?

2021-01-08T19:55:05.397700Z

that is the working example

2021-01-08T19:55:23.398100Z

I should try to make a small repro example

2021-01-08T19:55:33.398200Z

nils == empty seqs all over the place, unless there's a good reason to diverge. I'd say for (conj) that if you actually need it, it's likely you want append-to-the-end as it's the common case, so a vector is used. But for (conj nil) that's being very explicit (in my mind) that you want to append to the front.

2021-01-08T19:59:47.398800Z

Actually this is useful, as it is another invocation where the exception handler fails to register. It does not reference the precompiled indignant.core class file.

2021-01-08T20:03:16.399100Z

Thread/setDefaultUncaughtExceptionHandler is extremely brittle

2021-01-08T20:04:11.400Z

in this case my guess would be the difference you are seeing is between the way the clojure runtime loads and runs a main function with -m argument vs. how java loads and runs a main class

2021-01-08T20:04:53.400800Z

running a main class happens more directly, so there is very little code between you and the jvm, so any exceptions that happen bubble up and are "uncaught"

2021-01-08T20:05:53.401800Z

with -m clojure's error reporting, added somewhat recently, will catch exceptions and generate a report on them, usually in /tmp somewhere

2021-01-08T20:14:55.403600Z

The defaultuncaughtexceptionhandler is something you can set, for just in case, but you can never assume it will ever get called

2021-01-08T20:15:14.404200Z

It is the finallizer of exception handling

2021-01-08T20:59:59.405400Z

A good read: Clojure: the Lisp that wants to spread *"*Clojure is (slowly) eating the world" https://simongray.github.io/essays/spread.html

2021-01-08T21:04:08.407200Z

Ok I'll add a minimal repro in this thread for the issue that I'd like to understand: successful use of Thread/setDefaultUncaughtExceptionHandler seems to require AOT compilation

2021-01-08T21:04:42.407300Z

2021-01-08T21:05:28.407900Z

@waffletower see my explanation above

2021-01-08T21:05:49.408300Z

2021-01-08T21:06:37.409500Z

it isn't that using Thread/setDefaultUncaughtExceptionHandler requires aot, it is that when you run via an aot'ed -main method invoked via the jvm vs running via clojure's namespace loading mechanism

2021-01-08T21:07:04.410100Z

won't register the handler

clj -m uncaught.core

2021-01-08T21:07:12.410400Z

in those cases your code is being run in different contexts

πŸ‘ 1
2021-01-08T21:08:18.410500Z

does register the handler

mkdir classes
clj -e "(compile 'uncaught.core)"
java -cp $(clojure -Spath):classes uncaught.core

2021-01-08T21:08:31.410700Z

they both register the handler

2021-01-08T21:08:47.410900Z

in the clj -m uncaught.core the exception is caught

2021-01-08T21:08:58.411100Z

not in my case

2021-01-08T21:09:02.411300Z

it is

2021-01-08T21:10:11.411500Z

% clj -m uncaught.core
WARNING: When invoking clojure.main, use -M
Execution error (ArithmeticException) at uncaught.core/-main (core.clj:17).
Divide by zero

Full report at:
/var/folders/g5/1rcmn5_n4sn2scczzjtp8lyc0000gp/T/clojure-3856576569065612249.edn

2021-01-08T21:10:15.411700Z

% java -cp $(clojure -Spath):classes uncaught.core
exception handler caught exception on thread: main
#error {
 :cause Divide by zero
 :via
 [{:type java.lang.ArithmeticException
   :message Divide by zero
   :at [clojure.lang.Numbers divide Numbers.java 188]}]
 :trace
 [[clojure.lang.Numbers divide Numbers.java 188]
  [clojure.lang.Numbers divide Numbers.java 3901]
  [uncaught.core$_main invokeStatic core.clj 17]
  [uncaught.core$_main doInvoke core.clj 14]
  [clojure.lang.RestFn invoke RestFn.java 397]
  [clojure.lang.AFn applyToHelper AFn.java 152]
  [clojure.lang.RestFn applyTo RestFn.java 132]
  [uncaught.core main nil -1]]}

2021-01-08T21:10:23.411900Z

https://github.com/clojure/clojure/blob/master/src/clj/clojure/main.clj#L663-L667 is the code that clj -m ultimately invokes

2021-01-08T21:10:42.412200Z

you can see it runs you code wrapped in a try/catch with reporting stuff in the catch

2021-01-08T21:11:23.412400Z

again, if you read what I said previously in the channel, I explain this

2021-01-08T21:12:16.412600Z

I see what you are trying to say now. It gets caught in clojure main before it hits the handler

2021-01-08T21:13:28.412800Z

and in general, any place where you don't control the execution context of your code (callbacks, etc) might defensively wrap the execution of your code in a try/catch which will stop your exception from bubbling up

seancorfield 2021-01-08T21:13:45.413Z

For the future, #news-and-articles is a better place to post links to blog posts etc.

2021-01-08T21:13:49.413200Z

so, to quote myself Thread/setDefaultUncaughtExceptionHandler is extremely brittle

2021-01-08T21:14:16.413400Z

I disagree, and that is probably why I didn't read what you said after very carefully.

2021-01-08T21:14:46.413600Z

But you have helped me to look to clojure.main and how it wraps the code execution

2021-01-08T21:15:20.413800Z

using a jar invocation will allow this brittleness you speak of to disappear.

2021-01-08T21:15:48.414Z

nope

2021-01-08T21:16:46.414200Z

Ok, so if I recap: (conj nil 1) returns an empty seq, because nil and empty seq are kinda treated the same sometimes. So conjoining on nil is like asking to conjoin on empty seq. And then (conj nil) returns nil because conj is a transducer and the 1-ary for transducer for conj has to be identity. But why does (conj) return vector? It's an okay default, but so would have been return (), and that would be more consistent it seems. Are there any reason for vector instead? Is it also due to transducers? Or is there a common idiom which make this a better return?

2021-01-08T21:16:51.414400Z

it solves your specific case, but as I said, anytime you hand off your code to be run by someone else (callbacks, threadpools, etc) it is susceptible to this kind of thing

2021-01-08T21:17:09.414600Z

thank you

2021-01-08T21:18:07.414800Z

even if you aot compile in other cases you will still have this issue, and even with aot compiling you will still have this issue if you loading the aot compiled code via clojure.main instead of directly invoking its -main via java's main entry point stuff

NPException 2021-01-08T21:19:30.415100Z

The crucial point is that any try-catch (or thread-group specific exception handlers) outside of your control can stop exceptions from reaching your default exception handler.

πŸ‘ 1
2021-01-08T21:22:44.415300Z

if all you care about is the top level, you might want to look at https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#addShutdownHook(java.lang.Thread) instead, but that also has provisos and limitations

2021-01-08T21:23:29.415500Z

agreed. Being fairly new to clj deployments without jars I did not realize that the main thread was outside of my control given the implicit clojure.main try/catch. You folks helped me see that. Thanks!

2021-01-08T21:32:07.418500Z

it didn't use to, until early 2019 that try/catch wasn't there and the exception would bubble up

2021-01-08T21:34:11.420800Z

but beginners are constantly complaining about stacktraces being ugly, and there is a lot of hand wringing in some corners about beginners, so at some point a lot of work was done to try and make error reporting friendlier, and that is how that try/catch and the writing of the full stacktrace to a file in /tmp got introduced

2021-01-08T21:43:29.421800Z

What's your problem exactly?

2021-01-08T21:51:24.422300Z

I would put an entry on http://ask.clojure.org about this personally. It could be considered a regression is some way.

2021-01-08T21:51:43.422500Z

At the very least, its a breaking change

2021-01-08T21:52:35.422700Z

You could argue it should re-throw, and not run System/exit 1

Jack Arrington 2021-01-08T21:54:04.423300Z

Why is ISeq not implemented for PersistentVector?

2021-01-08T21:54:43.423400Z

Or you could argue that gen-class main https://github.com/clojure/clojure/blob/master/src/clj/clojure/genclass.clj#L448 should do a similar bootstrapping to clojure.main

2021-01-08T21:54:58.423800Z

I didn't experience it as a breaking change, personally, as I have until now universally used jar deployments. The difference in behavior was confounding at first, and now seeing the enclosing try/catch makes perfect sense.

2021-01-08T21:57:23.424500Z

because a vector isn't a seq

2021-01-08T21:59:58.426500Z

a seq is a basically a kind of iterator, so a similar question to consider is "why does a java.util.ArrayList return an iterator when you call the iterator method on it, instead of implementing instead of implementing java.util.Iterator directly"

Jack Arrington 2021-01-08T22:00:47.427100Z

PersistentQueue isn't a seq either, but it implements ISeq :thinking-face:

2021-01-08T22:01:51.427800Z

that is a mistake in the type hierarchy that can never be fixed πŸ˜•

πŸ‘ 1
😒 1
2021-01-08T22:02:38.428100Z

I still think it would be worthwhile to bring up. There have been changes in the past around consistency of the various ways Clojure bootstraps an app.

2021-01-08T22:03:00.428400Z

Personally, if possible, I don't see why you wouldn't want each mechanism to allow for the same basic assumptions

2021-01-08T22:03:58.429200Z

actually, that is not true

2021-01-08T22:04:09.429600Z

user=> (doseq [i (supers clojure.lang.PersistentQueue)] (prn i))
clojure.lang.IPersistentCollection
java.io.Serializable
clojure.lang.Counted
clojure.lang.IMeta
clojure.lang.Obj
clojure.lang.IHashEq
clojure.lang.Seqable
clojure.lang.IPersistentList
java.lang.Iterable
java.util.Collection
clojure.lang.IObj
java.lang.Object
clojure.lang.IPersistentStack
clojure.lang.Sequential
nil
user=>

2021-01-08T22:04:16.429800Z

no ISeq

2021-01-08T22:04:20.430Z

no mistake

2021-01-08T22:04:34.430100Z

If you look at the gen-class main, its so different to the clojure.main

2021-01-08T22:04:57.430300Z

It doesn't bind any dynamic var, it doesn't catch top level errors and report them, etc.

2021-01-08T22:05:21.430800Z

user=> (instance? clojure.lang.ISeq clojure.lang.PersistentQueue/EMPTY)
false
user=>

2021-01-08T22:05:23.430900Z

(conj) with zero arity was added the same time as transducers but it's not a transducer itself. But given that context: I think it's about expectations; what would you expect (transduce (map inc) conj '(1 2 3)) to return? I'd expect some sequence of 2 3 4 (i.e. the same ordering, so a vec is the only choice no-arity). If you do (transduce (map inc) conj () '(1 2 3)) (or with a nil) you'll get 4 3 2 But 🀷 I'm just guessing. It seems all fairly reasonable to me when I come to writing code.

2021-01-08T22:05:28.431200Z

I don't know, seems weird that an application would run with -main from clojure.main but not genclass -main

Jack Arrington 2021-01-08T22:06:03.432Z

Hm, weirdly there seems to be a divergence between Clojure and ClojureScript here. In CLJS, PersistentVector does implement ISeq: https://github.com/clojure/clojurescript/blob/946348da8eb705da23f465be29246d4f8b73d45f/src/main/cljs/cljs/core.cljs#L6295

2021-01-08T22:06:35.432800Z

as I mention every time this comes up, there are a ton of differences between cljs and clj

2021-01-08T22:06:42.433Z

everywhere

2021-01-08T22:07:16.433700Z

it is all different, you cannot assume they are the same anywhere

πŸ‘ 2
2021-01-08T22:09:09.434200Z

I'm curious how the new -X exec-fn setups the context

2021-01-08T22:09:19.434400Z

If that's also different from clojure.main and genclass main

vlaaad 2021-01-08T22:09:41.434600Z

> Are there any reason for vector instead? I think this is the wrong question to ask. You provide examples with nil: (conj) (conj nil) (conj nil 1) . Imagine the reverse situation if we had (conj) return () , and you thought about these examples:

(conj) => ()
(conj []) => []
(conj [] 1) => [1]
It would be fair to ask why not []?

seancorfield 2021-01-08T22:09:47.434800Z

As a Brit married to an American, I can confirm that! Even after 21 years of marriage we both still manage to say things that has the other one going "Huh?"

seancorfield 2021-01-08T22:10:46.435Z

But my wife has picked up some Cockney rhyming slang over the years πŸ™‚ and when she leaves me "honey do" notes, she just signs them T.

seancorfield 2021-01-08T22:11:01.435200Z

T is for Trouble. Trouble and strife = wife.

vlaaad 2021-01-08T22:11:56.435400Z

or why not #{} since conj preverses the type of coll on set as well: (conj #{}) => #{}, (conj #{} 1) => #{1}

2021-01-08T22:12:21.435900Z

In ClojureScript ISeq is a protocol, not an interface, that's why I believe

2021-01-08T22:13:13.437200Z

With protocols you don't have hierarchies, they are more like traits, so its fine to have PersistentVector extend the protocol to ISeq, but with interfaces it would cause weird type casting issues

vlaaad 2021-01-08T22:14:03.438200Z

zero-arity conj is for creating a default value for use in transducing/reducing context, and vector is a good default in this case.

2021-01-08T22:15:21.440400Z

On the jvm types are very restrictive, so the type hierarchy and how you arrange it has real consequences, and the relationships between the types tends to become part of the api. JS virtual machines are very different when it comes to types. So my guess is the hierarchy and relationships are carefully thought out in clojure, and in cljs it is just whatever is faster and mostly seems to be correctly

2021-01-08T22:17:37.441200Z

PersistentVector is not an instance of ISeq in ClojureScript either. It just extends the protocol

2021-01-08T22:17:50.441600Z

(instance? ISeq [])
;;=> false
(instance? ISeq #queue [])
;;=> false

2021-01-08T22:21:27.442400Z

My guess is nothing is actually an instance of ISeq, because its a protocol, nothing inherits from its type

2021-01-08T22:23:24.443300Z

Maybe more interesting is to look at seq? in Cljs

(seq? []) ; false
(seq? #queue []) ; true
(seq? '()) ; true

p-himik 2021-01-08T22:31:17.443500Z

Huh. Why does the #queue literal exist in CLJS but not in CLJ?

borkdude 2021-01-08T22:45:16.444800Z

What benefits do structs have over maps, if you can't use defrecord/deftype? I did a small test but accessing a key from a struct doesn't seem to be any faster than from a map - is that right?

borkdude 2021-01-08T22:49:19.445300Z

user=> (defstruct Foo :foo :bar)
#'user/Foo
user=> (def s (struct Foo 1 2))
#'user/s
user=> (time (let [s s] (dotimes [i 1000000000] (:foo s))))
"Elapsed time: 7141.330632 msecs"
nil
user=> (def s {:foo 1})
#'user/s
user=> (time (let [s s] (dotimes [i 1000000000] (:foo s))))
"Elapsed time: 6302.803225 msecs"
nil

lukasz 2021-01-08T22:50:20.446100Z

Are structs even used nowadays? I might be remembering it wrong, but they predate records

borkdude 2021-01-08T22:51:12.446900Z

Yes, they are replaced by deftype and defrecord, but I was considering using them for implementing something which can only be known dynamically

borkdude 2021-01-08T22:51:22.447100Z

E.g. they are still used in some CSV lib and I might have a similar use case for it.

borkdude 2021-01-08T22:52:36.447700Z

But if there is no significant win, I'm not sure why I should even bother

seancorfield 2021-01-08T22:59:15.448700Z

IIRC structs have the keys in a predictable order -- so using them for CSV stuff sort of makes sense

borkdude 2021-01-08T23:01:36.451200Z

ah, using a bigger example gives the expected result.

user=> (def ks [:a :b :c :d :e :f :g :h :i :j :k :l :m :n :o :p])
#'user/ks
user=> (def Foo (apply create-struct Foo ks))
#'user/Foo
user=> (def s (struct Foo 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6))
#'user/s
user=> (time (let [s s] (dotimes [i 1000000000] (:a s))))
"Elapsed time: 10425.754936 msecs"
nil
user=> (def s (zipmap ks (range)))
#'user/s
user=> (time (let [s s] (dotimes [i 1000000000] (:a s))))
"Elapsed time: 14030.239471 msecs"
nil
since smaller maps are backed by arrays

2021-01-08T23:07:49.452Z

you could use functions as tuples

2021-01-08T23:08:51.452700Z

you know, maybe the worst of all three options

borkdude 2021-01-08T23:10:37.452900Z

@hiredman eh, say what?

borkdude 2021-01-08T23:11:02.453200Z

I mean: I didn't understand where you are going with this, but I'm curious ;)

2021-01-08T23:15:04.453900Z

user=> (def ks [:a :b :c :d :e])
#'user/ks
user=> (def t (let [[a b c d e] ks] (fn [f] (f a b c d e))))
#'user/t
user=> (time (let [t t] (dotimes [i 1000000000] (t (fn [a & _] a)))))
"Elapsed time: 11304.311272 msecs"
nil
user=> 

2021-01-08T23:15:33.454200Z

scott encoding https://en.wikipedia.org/wiki/Scott_encoding

2021-01-08T23:16:50.455300Z

I was thinking about it because closed over values become fields in fns, which seems like it might be fast to access, but there are other issues with it that make it not great

2021-01-08T23:18:25.457100Z

1. it is not great because it is indexed access instead of by name 2. to do it really generically you have to use apply, which of course is not fast 3. in all likely hood you'll end up closing over a collection of values instead of individual values, so you get an extra hop

2021-01-08T23:21:54.458100Z

https://crypto.stanford.edu/~blynn/compiler/scott.html is a neat series building a mini haskell compiler that compiles down to lambda calculus (all data types are functions) and then from lambdas to ski combinators

πŸ‘€ 1
borkdude 2021-01-08T23:26:33.458400Z

thanks for sharing :)