Fork me on GitHub

Thanks @tbaldridge @smith.adriane @hiredman for the excellent feedback. I'm just not sure I get it regarding the comments about folds or fold-like reducing. Am I missing something really deep here?

dominicm09:12:40 Why does :keys use a keyword (`:person/name`) for destructuring namespaced keywords rather than a symbol (`person/name`)? Both work, but given this guide it seems the former is what you're expected to use.


@dominicm From the same page: "The :keys key is for associative values with keyword keys, but there are also :strs and :syms for string and symbol keys respectively."


@p-himik My question is about syntax, not about how to pull out symbols.


@dominicm using a keyword allows you to use

(:person/name person)
to get the name


I believe he's talking about the distinction when using :keys destructuring.

(let [{:keys [:person/name b]} {:person/name "Name" :b 2}] 
    [name b])
=> ["Name" 2]
(let [{:keys [person/name b]} {:person/name "Name" :b 2}] 
  [name b])
=> ["Name" 2]


The distinction between using :person/name as a keyword or person/name (as a symbol) Why the first seems encouraged if both work and the second seems more in line with how we destructure unqualified keywords.

Alex Miller (Clojure team)14:12:50

Symbols are preferred in my opinion. Either should work.


@dominicm I think it might be related to auto resolving keywords, possibly? Using ::some-ns/id would work only if some-ns was available in that scope, but using symbols some-ns/id would be happily accepted either way.


alias resolving keywords is probably a good reason. I wonder if symbols are supposed to work? e.g. the same way that (ns (require …)) worked until spec came in and caught everyone out.


That's a good question. I went with the flow of always using keywords according to what's in the available documentation, but I agree that it introduces a somewhat odd distinction.


symbols in :keys are supposed to work


destructuring syntax is already specced btw


@bronsa Should the guide be updated to mention that ns/name is supposed to work?

Alex Miller (Clojure team)13:12:47

:keys destructuring intentionally takes keywords to support the use of autoresolved keywords (either :: or : with an alias)

Alex Miller (Clojure team)14:12:07

Symbols are preferred though if you’re not leveraging those features

Alex Miller (Clojure team)14:12:59

Symbols can be either namespaced or not - the name part of the symbol is always what the bound local name will be


on the topic of autoresolved keywords, is there a jira ticket for being able for namespace aliases for non-existing namespaces?


just curious 🙂

Alex Miller (Clojure team)14:12:01

You mean as an enhancement?


@alexmiller I'm not familiar with : being used for autoresolved keywords, what do you mean by that?

Alex Miller (Clojure team)14:12:33

Sorry I meant :: with an alias. InsufficientCoffeeException


That's okay! That brings me onto my next question then, does that mean the guide is wrong in suggesting the use of :person/name and instead person/name should be preferred?

Alex Miller (Clojure team)14:12:48

Either is valid, I’d say symbol is preferred

Alex Miller (Clojure team)14:12:24

Usually we use symbols in :keys (as they then match the created locals)

Alex Miller (Clojure team)14:12:38

With qualified, it’s a little muddier


Yeah, until I knew about autoresolved keywords, the answer was very obvious. Now it feels like there's a special exception in :keys.


as an enhancement proposal, yes 🙂

Alex Miller (Clojure team)14:12:36

Re the enhancement, we have considered it (I actually implemented it) but Rich doesn’t want to change current state, however he has some ideas for an alternative for managing keyword aliases specifically


@alexmiller I'm curious, could you give a hint as to what Rich would like this to look like?


cool, I’ll keep an eye out, thanks!


what would you guys call this function?

(defn update-conj-vec
  "Takes map and adds v to the collection at k. If value at k is currently nil v
  is wrapped in a vector and set as k's value."
  [m k v] (update m k #(if (nil? %) [v] (conj % v))))


I’m not happy with it’s name…


@triss I've found that when I need that kind of function, I'm usually doing something wrong.


hmmm conj-at is kind of nice….


@dominicm care to elaborate? I’ve got some maps with lots of lists in that need adding too…. what gives you that feeling?


oh, I misread the docstring. Apologies.


@triss (conj nil 1) does work btw. Is it important that it's a vector?


@triss Not that you asked, but I'd write that like so:

[m k v]
(update m k (fnil conj []) v)


"Takes a map and conj's v to the collection at k. If the value at k is currently nil, a vector will be used as the collection type" is how I would word the docstring, the "how" of wrapping the v is too precise.


oh fnil looks kind of handy. Thanks!


@triss however, if you're adding values to lots of keys, perhaps two generic functions would work together better? (def vec-conj (fnil conj [])) and a function to update-keys for updating many keys with the same fn.


just vec-conj has tidied things up plenty. Cheers man!


user=> (def vec-conj (fnil conj []))
user=> (defn update-keys [m ks & args]
#_=> (reduce (fn [m k] (apply update m k args)) m ks))
user=> (update-keys {:k nil :v [1 2] :c (list :a)} [:new :k :v :c] vec-conj :new-val)
{:k [:new-val], :v [1 2 :new-val], :c (:new-val :a), :new [:new-val]}
As I was toying around 🙂


Is first lazy?


(let [s (lazy-seq
          (println "Called!")
          (cons :foo nil))]
  (first s))
=> :foo

Alex Miller (Clojure team)16:12:15

How could first be lazy?


fst :: (a, b) -> a 🙂

Alex Miller (Clojure team)16:12:30

well, Clojure has eager evaluation semantics so it’s not lazy in that sense


agreed. fst in haskell isn't lazy, haskell is lazy

Alex Miller (Clojure team)16:12:03

it just lays around all day waiting for something to happen, psh


so do I. it's a wonder haskell and I never got on


you have to sufficiently motivate it. otherwise it just prints nah and dies in constant time

New To Clojure16:12:11

@chris you could also try if it's needed to run it on JVM

New To Clojure16:12:29

Is trampoline's performance good? There's an JavaScript <|article> (Clojure trampoline implementation seems to be similar) and it says: >The trampoline implementation has essentially the same number of function invocations as the traditional recursive implementation. A TCO would eliminate these function invocations. >We’ve traded the work of creating stack frames with creating function bindings. Both operations consume time and memory. TCO doesn’t create function references due to its modifying the function implementation in place to transform the recursive implementation to a loop. So it seems that trampoline isn't a substitute for TCO.


while it is right that the number of function invocation stays the same, the big difference is that those stack frames don't exist in memory all at once but they exist one at a time


nobody claimed that trampoline is a substitute for TCO


trampoline is a workaround for a problem that is also solved by TCO


isn't trampoline usually how TCO is implemented under the hood, as well?


general TCO transforms function call and return into jumps


@dpsutton it's interesting, on the x86 the place the code jumps to on a return is simply a pointer on the stack. So you can implement other models that patch up that value to do stuff like TCO or continuation passing. Sadly this is also fairly insecure, so the JVM abstracts all that away, and doesn't give you control over the return address.


heh, and these days, with branch prediction, procedure calls are practically free 😄


sure, but the compilation strategy in the paper generates code where calls are just a jump without a pushed return address unless required (looks like cps or a precursor), of course it predates worrying about the x86 calling conventions, because people were still arguing about if subroutines were a good idea


practically free... at the cost of huge silicon wastage


or higher power envelope


> Interprocedural Specialization of Higher-Order Dynamic Languages Without Static Analysis This looks like a promising avenue for optimizing functional languages like Clojure


is there an easy way to downgrade lein?


well apparently lein updade 2.7.1 will upgrade to a specific version


same works for downgrading


i.e. lein update 2.5.0 will up/downgrade to that version


Just curious, why the downgrade?


we have a plugin not working with 2.8.1, last known is 2.7.1


slothconfig i think is the culprit


Does anyone recommend a library for writing isomorphic data conversions/bijections? Basically being able to write one implementation to go from type a -> type b, and getting type b -> type a "for free"


is that even possible? it sounds kinda like


where you can zoom in on part of a datastructure, perform transformations locally, and then zoom out


@quest, clojure prefers data (hashmaps vectors) instead of types, so I'm not sure this applies?


there are a lot of libraries that do things like define some base conversion functions, then create a graph of all the types, then use the graph and base conversion functions to generate the rest of the conversion functions


@smith.adriane possibly, and specter seems nice, but not quite it @tbaldridge Clojure doesn't emphasize types, but they're still implicitly there. You'll see something like (defn a->b [a] ...), which (by convention) is a typecast from a to b @hiredman I saw which seems to fit. Know of any others?


@quest but what types are you trying to convert. If a and b are records, then (map->b a) is automatic


as is (map->a b)


I have two different representations of the same thing. I want to write a single set of rules that goes from a to b, and get the reverse function (b to a) automatically


That's what I'm getting at, why do you have two representations of the same thing? Why not store them in one format?


Keep the data in hashmaps, or even records, and then stuff just works.


also - it really depends on what data transformation you are talking about, it’s provable that you can’t write a general function for such conversions so there must be other constraints implied here


"two representations of the same thing"


Because the things in question are events, and the "business event" looks different from the event that's sent out to the external event service. There's no magic solution of "use the same format" here


sounds good here


that sounds like a data modeling problem. Chances are that with namespaced keywords and hashmaps your required transformations would be quite minimal


{:event/timestamp ...} can exist in any hashmap that's an event with a timestamp


and if you have {:service.event/timestamp ...} then its a question if you really need to discriminate between a service event timestamp and a ui event timestamp


what does an "event" look like quest? I think i follow you and i see what you are trying to do


I'm saying all this because the last time I needed a library like this I was in a C# app where a good 40% of all our code was nothing more than type conversions. Moving to Clojure removed the need for that.


sounds like there's a notion of "add a customer", and this app calls it create-customer and some other apps call it customer-create


and you just want to translate back and forth?


OK -- for instance, our business event contains various fields.

{:foo-id "abcde"}
The external service we publish them to supports "custom fields" via a facts map.
{:facts {:foo-id "abcde"}}


All I want a way to specify this transformation that will work "forwards and backwards"


I can handroll this easy enough, just figured there was a "declarative" way to specify these transformations. I notice someone made which is the same idea


it’s not a standard or built in thing - that lib is the closest thing I’ve seen


fair enough. I can kinda imagine a way to do this via transducers if they each one in the chain was "reversible", but never seen that either


I misunderstood, thought you wanted to describe a one way transition and have the reverse defined automatically


clojure has no way to define, declare, or detect reversibility


yeah -- no way you could do this "automatically" without also having definitions of what (and how) things can be reversed


though if you limit yourself to purely isomorphic translations (and your library understands that), then it could generate the reverse for you


I wouldn’t be surprised if the haskell or idris compiler did such things, but clojure’s compiler doesn’t even aim to be clever like that


that’s true - you could make your own declarative language for transforms


might be a fun thing to try in eg. core.logic


hmm... that's true, and finding out how to mesh this with specs would be an interesting (and relevant) challenge for me. good idea 🙂


having known gfredericks for a while, my instinct when something he wrote comes up is that either I’m on track of something very clever and useful or something very clever and … perverse


(known online that is, just hung out with him at a couple conferences)


haha - I’ll stop now


I get what you mean by perverse 😁


there’s a lot of overlap with what you’re describing and specter. basically you have a bunch of reversible operations. the library has a specific use case in mind (manipulating immutable nested data structures), but you could in theory have navigators that are just reversible operations (i think)


i’m not saying it’s a good fit for what you’re doing, but you may be able find/borrow inpirations


didn't even consider that. Specter does have a lot of nice stuff going on under the hood, will also take a look at that


it’s clear that you could use select and a path to go from one type to another


not sure if you could use transform to automatically do the reverse given the same path


(a path being a list of navigators)


which you would be repurposing as reversible operations


don't know enough about Specter -- but worst case, as long as there's a set of operations, I can manually denote which ones are reversible (and how). whether that ends up being an extension of specter, core.logic, or a different library entirely... we'll see