Fork me on GitHub
#clojure
<
2017-12-05
>
matan07:12:43

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

https://clojure.org/guides/destructuring#_namespaced_keywords 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.

p-himik09:12:09

@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."

dominicm09:12:17

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

gklijs10:12:24

@dominicm using a keyword allows you to use

(:person/name person)
to get the name

andre.stylianos11:12:34

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]
vs
(let [{:keys [person/name b]} {:person/name "Name" :b 2}] 
  [name b])
=> ["Name" 2]

andre.stylianos11:12:42

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.

andre.stylianos11:12:35

@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.

dominicm11:12:22

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.

andre.stylianos11:12:59

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.

bronsa11:12:46

symbols in :keys are supposed to work

bronsa11:12:43

destructuring syntax is already specced btw

dominicm11:12:59

@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

schmee14:12:23

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

schmee14:12:31

just curious 🙂

Alex Miller (Clojure team)14:12:01

You mean as an enhancement?

dominicm14:12:06

@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

dominicm14:12:07

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

dominicm14:12:07

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

schmee14:12:21

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

dominicm14:12:27

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

schmee14:12:34

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

triss14:12:16

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))))

triss14:12:50

I’m not happy with it’s name…

dominicm14:12:59

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

triss14:12:09

hmmm conj-at is kind of nice….

triss14:12:21

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

dominicm14:12:57

oh, I misread the docstring. Apologies.

dominicm14:12:38

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

dominicm14:12:39

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

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

dominicm14:12:48

"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.

triss14:12:22

oh fnil looks kind of handy. Thanks!

dominicm14:12:36

@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.

triss14:12:01

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

dominicm14:12:18

user=> (def vec-conj (fnil conj []))
#'user/vec-conj
user=> (defn update-keys [m ks & args]
#_=> (reduce (fn [m k] (apply update m k args)) m ks))
#'user/update-keys
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 🙂

Ilya15:12:41

Is first lazy?

mikelis16:12:19

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

Alex Miller (Clojure team)16:12:15

How could first be lazy?

dpsutton16:12:57

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

dpsutton16:12:04

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

chris16:12:18

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

dpsutton16:12:25

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 http://eta-lang.org/ if it's needed to run it on JVM

New To Clojure16:12:29

Is trampoline's performance good? There's an JavaScript <https://taylodl.wordpress.com/2013/06/07/functional-javascript-tail-call-optimization-and-trampolines/|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.

bronsa16:12:03

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

bronsa16:12:42

nobody claimed that trampoline is a substitute for TCO

bronsa16:12:13

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

dpsutton17:12:20

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

bronsa17:12:11

general TCO transforms function call and return into jumps

tbaldridge17:12:25

@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.

tbaldridge18:12:06

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

hiredman18:12:30

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

ghadi18:12:22

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

ghadi18:12:40

or higher power envelope

ghadi18:12:23

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

dpsutton19:12:22

is there an easy way to downgrade lein?

dpsutton19:12:03

well apparently lein updade 2.7.1 will upgrade to a specific version

danielcompton20:12:09

same works for downgrading

danielcompton20:12:22

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

ghadi20:12:52

Just curious, why the downgrade?

dpsutton20:12:46

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

dpsutton20:12:21

slothconfig i think is the culprit

Quest22:12:35

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"

phronmophobic22:12:43

is that even possible? it sounds kinda like https://github.com/nathanmarz/specter

phronmophobic22:12:11

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

tbaldridge22:12:37

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

hiredman23:12:21

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

Quest23:12:29

@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 https://github.com/twitter/bijection which seems to fit. Know of any others?

tbaldridge23:12:46

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

tbaldridge23:12:52

as is (map->a b)

Quest23:12:45

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

tbaldridge23:12:17

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

tbaldridge23:12:32

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

noisesmith23:12:06

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

dpsutton23:12:28

"two representations of the same thing"

Quest23:12:30

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

dpsutton23:12:31

sounds good here

tbaldridge23:12:37

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

tbaldridge23:12:00

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

tbaldridge23:12:41

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

dpsutton23:12:24

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

tbaldridge23:12:37

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.

dpsutton23:12:04

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

dpsutton23:12:12

and you just want to translate back and forth?

Quest23:12:56

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"}}

Quest23:12:40

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

Quest23:12:07

I can handroll this easy enough, just figured there was a "declarative" way to specify these transformations. I notice someone made https://github.com/gfredericks/schema-bijections which is the same idea

noisesmith23:12:03

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

Quest23:12:40

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

noisesmith23:12:53

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

noisesmith23:12:07

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

Quest23:12:04

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

Quest23:12:42

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

noisesmith23:12:47

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

noisesmith23:12:02

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

noisesmith23:12:35

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

Quest23:12:05

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

noisesmith23:12:34

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

noisesmith23:12:56

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

noisesmith23:12:02

haha - I’ll stop now

Quest23:12:36

I get what you mean by perverse 😁

phronmophobic23:12:06

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)

phronmophobic23:12:11

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

Quest23:12:55

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

phronmophobic23:12:31

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

phronmophobic23:12:49

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

phronmophobic23:12:35

(a path being a list of navigators)

phronmophobic23:12:01

which you would be repurposing as reversible operations

Quest23:12:53

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