Fork me on GitHub
#beginners
<
2018-11-28
>
jesse.wertheim00:11:19

@dpsutton @jstaab nice! that's occurred to me from time to time as a missing convenience. the downside to that succinct-map is that it's all-or-nothing since, unlike JS where each property is separated by a comma, in clojure maps the commas are ignored/optional. For that reason, this is what I came up with - basically just wraps hash-map but with a special symbol for including short-hand values alongside the explicitly defined pairs:

jstaab00:11:48

@jesse.wertheim totally tangential, but why mapcat and hash-map instead of just into {}?

jesse.wertheim00:11:37

very important reasons that have nothing to do with me just getting into a logic hole πŸ˜„

jstaab00:11:51

ha fair enough

jesse.wertheim00:11:57

now I'ma update it though

jstaab00:11:00

'sokay, it got me to finally look up mapcat, so I learned

jesse.wertheim00:11:08

initially I did it a bit differently, such that you'd do (short-map :a 1 :b 2 (succ c d)) and it made more sense to use the sequence functions but yeah, into definitely makes more sense with the way I ended up putting the usage

jesse.wertheim00:11:30

since the former way didn't involve partition

jesse.wertheim01:11:31

@jstaab actually it's a lot easier without into since we're manipulating forms and syms

jesse.wertheim01:11:11

at least I'm having a harder time writing it with into

jesse.wertheim01:11:34

which is funny because it's my bread and butter outside of macros

jstaab01:11:36

I suspected that, does this not work?

(defmacro short-map [& xs]
  `(into {} [email protected](mapcat
         (fn [pair]
           (if (= 'succ (first pair))
             (mapcat #(vector (keyword (name %)) %) (second pair))
             pair))
         (partition 2 xs))))

petterik01:11:55

Here's one that's very close to what you have:

(defmacro short-map [& xs]
    (into {}
          (mapcat (fn [[k v]]
                    (if (= 'succ k)
                      (map (juxt keyword identity) v)
                      [[k v]])))
          (partition 2 xs)))
It just outputs the map with symbols and keywords. See this macro expansion:
(macroexpand-1 '(short-map :a 1 succ [b c] x))
{:a 1, :b b, :c c}

petterik01:11:58

^ @jesse.wertheim if you're interested

jesse.wertheim01:11:49

ahh there we go! knew there had to be a cleaner way and there we have it

jstaab01:11:54

Hmm I guess not, the splice is messing it up

jesse.wertheim01:11:15

yeah, since what you need in that case is a sequence of tuples

jstaab01:11:49

Oh yeah, that too

jstaab01:11:04

ah well, if it wasn't the end of the workday I'm sure we could come up with something succinct and perfect πŸ˜›

jesse.wertheim01:11:39

double-mapcat works for me! there's some overhead due to the nested mapcat but since we're just manipulating inputs in a macro that's not likely to be super relevant (though you could always use eduction, at least, if you really wanted to optimize away the pointless caching)

jstaab01:11:36

Nice, very cool. I gotta look up eduction. Arguably harder to read than the original though

jesse.wertheim01:11:49

true. probably not worth the tiny perf savings

jesse.wertheim01:11:02

the tldr on eduction is that it's basically the same as sequence but instead of a lazy-seq (which caches), it produces a sequable/iterable - really only useful as an optimization when you're producing something to be immediately consumed (making the lazy-seq caching pointless)

jstaab01:11:44

gotcha, good to know. So a performance escape hatch based on mutability

joseph.guhlin01:11:59

Hey, I feel like there is a macro or fn to do this. Is there a way to take a sequence [x y z] and a sequence of functions [fnx fny fnz] and returns the result of [(fnx x) (fny y) (fnz z)] ? I feel like what I'm doing is over complicating things. Thanks all.

jstaab01:11:55

map can take multiple arguments, so (map #(%1 %2) [inc dec] [1 2]) ;; => (2 1)

jstaab01:11:03

Though I'd be interested if there's a named function that does that lambda

jstaab01:11:41

Oh hey, (map 'apply [inc dec] [1 2]) ;; => (2 1), though I'm surprised the quoted form worked

joseph.guhlin02:11:05

the second just returns (1 2) for me in my repl (clojure 1.10) not sure why though.

joseph.guhlin02:11:06

first one looks cleaner than what I've got, also surprised if there isn't something for it though. Parsing a file and need to turn some fields to numbers with read-string, rest are just treated with identity

jesse.wertheim02:11:55

my guess is because it's relatively simple to implemented directly as shown in that first example using map

joseph.guhlin02:11:32

Yeah, just with 12 arguments it gets a little ugly with the %'s, and clojure is so beautiful, but it works and is much cleaner than my previous code. πŸ™‚ thanks

joseph.guhlin02:11:59

ope, my mistake, the map there works without going up to %12. need more coffee, thanks. awesome

jstaab03:11:22

@joseph.guhlin my bad with mapping 'apply, you're right that it doesn't do what you were looking for. I was just messing around, but apparently symbols are 1 or 2 arity functions, that act like identity of argument #2? What's going on there?

joseph.guhlin03:11:13

No worries here. Not sure what is going on with 'apply, but it is weird. ('yello inc 34) => 34 (just randomly typing a bit in the repl)

alexmiller03:11:28

symbols are functions that when given an associative collection, look themselves up as keys in it

alexmiller03:11:37

and if not found, use arg 2

alexmiller03:11:11

(sym map not-found) is basically same as (get map sym not-found)

joseph.guhlin03:11:16

ah yeah, a shortcut for get, just used to the : before them instead of a ' but they are the same

alexmiller03:11:31

yeah, same as kw

alexmiller03:11:13

('foo {'foo 10}) => 10

alexmiller03:11:41

('bar {'foo 10} 20) => 20

jstaab03:11:48

Ah ok got it

jstaab03:11:07

I was just starting to browse through clojure's java code, which was interesting

quieterkali03:11:06

can anyone help me with schema "s/conditional" use example please?

trailcapital03:11:20

There is an example in the README https://github.com/plumatic/schema

(def StringListOrKeywordMap (s/conditional map? {s/Keyword s/Keyword} :else [String]))
Is there something beyond that which you are curious about?

quieterkali03:11:56

thank you @trailcapital πŸ™‚ , i really need to learn how to look for stuffs 😞

mseddon13:11:54

If anyone has a moment, I'd love to hear any feedback on this https://gist.github.com/mseddon/c306bfc5421badb72b68f3b422b3fd4d

mseddon13:11:21

it works, but I wonder if it's too opaque or could be generally improved

tavistock13:11:34

it’s fairly readable to me. is (merge-with + tl { expr 1 }) the same as (update tl expr inc)? build-env is a little terse maybe use a ->>

tavistock13:11:23

is there a paper you’re following for the algorithm? or something that explains it, I’m interested

tavistock13:11:38

ahh, (merge-with + tl { expr 1 }) sets 1 if it doesn’t exists, update inc isnt the same, nvm

mseddon14:11:04

ah yeah πŸ˜‰ sorry I had stepped out

mseddon14:11:48

there's no real paper on the algorithm- what it's doing is finding all duplicate branches in the tree and allocating symbols for them instead

mseddon14:11:03

I've written a cheesy algebraic simplification algorithm, and i'm using that in combination with this to automatically expand some otherwise very tedious fixed size matrix library routines.

mseddon14:11:14

incidentally the simplifier https://gist.github.com/mseddon/dedf80760a8ec32a1a7d4e108e6fca12 tries to put expressions into a canonical form, so by running it first, the common subexpressions are more likely to match... e.g. (+ y x z) turns into (+ x y z) after the simplification process

mseddon14:11:18

Though that code is probably quite opaque. perhaps I should start a blog...

tavistock14:11:58

idk if the helps but you might be able to write a more β€œsuccinct” (read opaque) version of the simplifier using core.logic

mseddon14:11:12

Yeah, I've been looking at that- it has some potential I think. The simplifier itself would be perfectly "readable" in prolog, after all.

mseddon14:11:23

oh, nice find!

mseddon14:11:42

thanks I will look into that, I do love me some highbrow lisp meta programming πŸ˜‰

tavistock14:11:37

sorry to link dump

mseddon14:11:24

have at it! πŸ™‚

mseddon14:11:02

naughty elision of the occurs check in that unify πŸ˜‰

kari.marttila16:11:29

It would be interesting to know how much seasoned Clojure programmers are using various IDEs (and which IDEs) or are they mostly just hacking with some text editor + clj cli?

kari.marttila16:11:25

And are they using Leiningen or Boot or just clj cli + deps.edn?

kari.marttila16:11:38

And are they using a lot type hints?

trailcapital17:11:41

Another datapoint: Where I work (60-75 devs working in Clojure(script)) I believe most people are using intelliJ (70%), then emacs (~15-20%), then others. I am one of two people who use vim. For all of our apps we use lein.

kari.marttila17:11:35

Wow! 70 developers working with Clojure. I'd like to be there. πŸ™‚

kari.marttila17:11:26

It's nice that I found this Clojure Slack community again. I don't feel so lonely with Clojure any more. πŸ™‚

jaihindh.reddy17:11:14

http://blog.cognitect.com/blog/2017/1/31/clojure-2018-results Emacs CIDER and IntelliJ Cursive are the by far the most popular. Although deps.edn is catching up, I bet leiningen still is at the top.

kari.marttila17:11:27

Thanks. That was interesting to read.

toby92416:11:10

@kari.marttila as one datapoint, my team uses IntelliJ and Cursive predominantly, as we have a number of different languages in use alongside Clojure. We're using Leiningen and only use type hinting when our linter complains about reflection around Java interop.

d.ian.b17:11:42

Hey guys, I'm new to Lacinia

d.ian.b17:11:11

I'm having a problem that lacinia queries and mutations is returning me clojure maps

d.ian.b17:11:46

I have to define a type on lacinia's schema.edn to return me every key to a graphql like output?

d.ian.b17:11:50

query:

{  sendSms(phone: "+5552999991000") }
response:
{
  "data": {
    "sendSms": "{:message \"SMS token was sent\", :success true, :uuid 109507373}"
  }
}

orestis17:11:42

@d.ian.b perhaps #graphql would be an easier place to get an answer on this.

orestis17:11:15

We’d need to see your defined schema to help with this.

d.ian.b18:11:48

{:queries {:requestAccessToken
           {:type String
            :args {:uid {:type (non-null String)}}
            :resolve :query/request-access-token}

           :sendSms
           {:type        String
            :args        {:phone {:type String}}
            :resolve     :query/send-sms}}}

mseddon17:11:08

hm. (merge-with op m0 m1) should preserve the order of m0, IF m0 is an array-map, right?

mseddon17:11:51

for 57 iterations, new keys are appended onto the END of m0, yet the next one inserts the value onto the beginning...

mark54018:11:16

https://clojure.org/reference/data_structures#ArrayMaps

Note that an array map will only maintain sort order when un-'modified'. Subsequent assoc-ing will eventually cause it to 'become' a hash-map.

mseddon18:11:32

and it appears to happen ~ 30 keys

andy.fingerhut18:11:50

It should be closer to 8 or 16 keys, if I remember correctly. Perhaps the keys you are using just happen to be in the hash-map order?

andy.fingerhut18:11:13

If you want a map that preserves key insertion order, there is an ordered-map data structure library, linked to from the Clojure cheatsheet in the "Sorted map" section: https://clojure.org/api/cheatsheet

andy.fingerhut18:11:37

There are also sorted maps that sort the keys according to a comparison function you provide, built in.

andy.fingerhut18:11:40

But note that the return type of (merge m1 m2) will be the type of m1, not m2, so if you call a function with a map m that inside of that function it calls (merge m1 m2) and returns the result to you, you won't get your order preserved.

mark54018:11:53

There is a sorted-map that provides key order, not insertion order. I don't see an ordered-map.

mark54018:11:37

Oh, ordered-map is in a separate project, I see now.

mseddon18:11:17

ah, that's worth a look, thanks!

mseddon18:11:00

for now I just split it into a pair, a vector of keys and a map, basically I wanted to build a map but also collect a post-order walk order

mseddon18:11:55

good to know though, since anything beyond this smaller use-case would become a pain quickly.

mseddon18:11:21

ah. hmm. somewhere deep in my code it gets mangled. nasty.

d.ian.b18:11:01

my schema edn.clj

orestis18:11:49

I don’t see a sendSms in there? Nor any types.

orestis18:11:27

Sorry, I need to go, do ask this also in #graphql and you will get a more focused audience.

d.ian.b18:11:03

mistook the schema.edn

d.ian.b18:11:23

I'll change here

d.ian.b18:11:18

@orestis changed, sorry

kari.marttila18:11:07

Is there some simple way which doesn't impact performance too much to automatically log the name of the function. At the moment I add the function name manually like: (deftest get-products-test (log/debug "ENTER get-products-test") ... ... which logs: 2018-11-28 20:43:55 DE [nREPL-worker-5] DEBUG s.domaindb.domain-single-node - ENTER get-product-groups I'm using clojure.tools.logging with logback.xml configuration.

tkjone19:11:20

I have a spec that looks like this:

(s/def ::my-tuple (s/tuple keyword? keyword?))
Lets say I have a function that maps over my tuple and changes the items from strings -> keywords
(defn my-func 
    [tuple]
   (map keyword tuple)

=> (my-func ["pos-1" "pos-2"])
=> (:pos-1 :pos-2)
The thing is, the map is going to return a lazy-seq which seems to mean that ::my-tuple will not be valid when passed (:pos-1 :pos-2). My question is that s/tuple seems to be the right thing to do for this spec (the collection is fixed position), but because the map in the function returns a lazy-seq, it will fail the spec. Would this suggest that I just have to force the lazy-seq back to its original structure and we are good or maybe I am thinking about this wrong?

tkjone19:11:42

FYI - my solution is to use mapv over map, but I wanted to know if there are other ways to consider this.

mark54019:11:16

I haven't tried it but maybe (s/coll-of keyword? :count 2) would work, if there is no need to return a vector.

tkjone19:11:34

I figured that tuple would be more appropriate as it communicates that its fixed position?

lennart.buit19:11:43

Well, if its a two tuple, I don’t see much value in keeping it in a seq

lennart.buit19:11:07

unless computing those two values is a performance disaster πŸ˜›

mark54019:11:27

Looks to me like tuple is mainly useful for mixed types. I'm not sure why it has to be indexed.

lennart.buit19:11:01

(you know, if you had a prime factorisation tuple or something)

mark54019:11:17

map returns a sequence, so it is wasteful to convert to a vector when this is not needed.

lennart.buit19:11:52

not arguing that, but it is not super wasteful with a 2-tuple

mark54019:11:44

yes, maybe it would be simpler, depending on the code that's using it.

mark54019:11:39

Still it seems strange to me that s/tuple implies indexed?.

mark54019:11:46

But maybe that's tuple's intended meaning. The doc just says "positional".

mark54019:11:27

So if you don't want indexed you could also use s/cat.

noisesmith19:11:35

have you tested this assumption that a two element vector is more wasteful than a two element lazy-seq? given the map transducer you don't need to produce the seq first

noisesmith19:11:17

(and mapv does exactly this, uses the transducer, thus not creating a seq)

mark54019:11:22

Good point!

didibus20:11:23

It doesn't get any simpler than mapv

didibus20:11:12

Are you looking for something more general?

noisesmith20:11:46

so - actually testing it, mapv on two items is nearly twice as fast as map on two items all else being equal

(ins)user=> (crit/bench (doall (map keyword ["a" "b" "c"])))
Evaluation count : 119700720 in 60 samples of 1995012 calls.
             Execution time mean : 349.709449 ns
    Execution time std-deviation : 51.209511 ns
   Execution time lower quantile : 317.752122 ns ( 2.5%)
   Execution time upper quantile : 501.633441 ns (97.5%)
                   Overhead used : 1.848273 ns

Found 7 outliers in 60 samples (11.6667 %)
	low-severe	 1 (1.6667 %)
	low-mild	 6 (10.0000 %)
 Variance from outliers : 84.1226 % Variance is severely inflated by outliers
nil
(cmd)user=> (crit/bench (mapv keyword ["a" "b" "c"]))
Evaluation count : 331083300 in 60 samples of 5518055 calls.
             Execution time mean : 184.412115 ns
    Execution time std-deviation : 6.374979 ns
   Execution time lower quantile : 176.007592 ns ( 2.5%)
   Execution time upper quantile : 202.071163 ns (97.5%)
                   Overhead used : 1.848273 ns

Found 3 outliers in 60 samples (5.0000 %)
	low-severe	 3 (5.0000 %)
 Variance from outliers : 20.6437 % Variance is moderately inflated by outliers
nil

lennart.buit20:11:46

Maybe a bit more of an architectural question, I have this codebase now that has quite a lot of protocols, and I end up writing similar implementations for those protocols in the most usual cases. Naturally I want to remove that duplication. In Haskell, there is deriving for generating this boilerplate for the most common cases, is there something similar in Clojure?

didibus20:11:06

I'm confused, different functions across different protocols share the same implementation?

lennart.buit20:11:34

no, many records implementing the same protocol in similar ways

didibus20:11:53

Ah I see, you're looking for a default implementation

lennart.buit20:11:56

(but not always!)

lennart.buit20:11:25

Yeah basically

didibus20:11:27

Just define an impl for Object

didibus20:11:42

Assuming JVM clojure here

lilactown20:11:02

☝️ but FYI that's going to extend the protocol to any type

lennart.buit20:11:13

I dont quite like that tbh

lilactown20:11:41

you could just put the "usual" impl's in functions πŸ˜›

lennart.buit20:11:53

yeah my haskell is leaking here

didibus20:11:50

Right, if you want explicit default impl, then just do that. Have a function that isnthe default impl and explicitly extend each type you want with it

lennart.buit20:11:31

I think I will make my protocols extend a deriving multimethod with some default impl

didibus20:11:38

If you use extend

didibus20:11:49

They can all share the same function

lennart.buit20:11:51

so I can just do like (deriving MyProtocol MyType)

lennart.buit20:11:16

hmm yeah thats cool!

didibus20:11:17

What's deriving?

lilactown20:11:17

I've also kinda solved a similar problem in the past by passing in the implementing objects as parameters to the user-facing type

lennart.buit20:11:23

(Deriving is the way that haskell calls it, if you have some datatype in Haskell, you can say deriving (Show), giving you some default toString method)

didibus20:11:57

Hum, I guess you could write something similar. Though I get the impression you might be overcomplicating things and trying to do a heavy type driven style of programming, which isn't Clojure's forte.

didibus20:11:18

But you could do something like

lennart.buit20:11:03

yeah, will try to reduce that going forward

lilactown20:11:28

I guess you need to ascertain which way your similarities are. is this something that could be solved by passing in the implementation at object creation, or something that belongs in the hierarchy?

didibus20:11:56

(def default-x-impl {:a (fn...) :b (fn...)})
(extend myType MyProto default-x-impl)

didibus20:11:47

Then you could write a macro that takes many types and extend them all with a given proto + protocol map

didibus20:11:13

So you could easily define a default for many types

didibus20:11:59

Or, another macro you could write would take a type, a protocol and a protocol map and then a list of "overriding" method to function.

didibus20:11:57

And inside that, if there is an override you just assoc it into the map before extending. Something of that sort.

lilactown20:11:46

all of this is kind of predicated that all of your similarities are similarities in an is-a? kind of way

lennart.buit20:11:25

I am kinda lost on what you were trying to say lilactown, could you elaborate?

didibus20:11:34

So you'd do (deriving MyProtocol MyType default-MyProtocol-impl (some-method [this a] ...))

lilactown20:11:55

it's the whole composition vs. inheritance thing. I try and be careful, when I see that I have a bunch of code that is similar, to figure out if it's because they all derive from the same abstraction ("this is a car") or if they are composed of similar utilities ("it has an engine")

lilactown20:11:00

for instance, here's a rough approximation of how we wrote our data fetching layer in one of our applications:

(defprotocol IRemote
  (read! [conn query config])
  (write! [conn mutation config]))

(defprotocol ICache
  (read [store query config])
  (write! [store mutation config value])
  (reset! [store]))

(defprotocol ISource
  (read! [source query config])
  (write! [source mutation config]))

(deftype SourceImpl [remote cache]
  ISource
  (read! [this query config] ;; implement standard cache check / fetch from remote
    )
  (write! [this mutation config] ;; implement standard update cache / modify remote
   ))

lennart.buit20:11:10

right so thats composition

lilactown20:11:38

90% of our data sources were REST endpoints that just used a really simple way of caching requests in an atom. so we have a DefaultCache implementation, and write the implementation of the IRemote to hit the specific REST endpoint we wanted

lennart.buit20:11:21

Right that makes sense

lilactown20:11:33

anyway, it might not be at all what you're looking for. I was just prodding to see if composition might fit your problem better

lennart.buit20:11:00

yeah come to think about it, I have some composition as well

lennart.buit20:11:37

but I believe that larger parts of my appliance call for a more traditional inheritance type of design

lennart.buit20:11:59

Or… inheritance is the wrong name

lennart.buit20:11:08

Well it kinda is

lennart.buit20:11:43

Thanks all for pitching in btw, you guys rock!

lennart.buit20:11:56

Make my clojure learning curve less steep

didibus20:11:08

I never want to say never, because there's always a time and place for anything. But in general I'd advise you first try to use existing data-structures and functions grouped in namespaces. If that really doesn't work, I might look into protocols and records, no hierarchy. If you need a hierarchy, defmulti might be more suited, since protocols are not intended for that. And finally, I'd look into deftype.

lennart.buit20:11:42

Maybe I went a bit far with protocols, but I think that there is certainly value in it for me right now

didibus20:11:31

Ya, you know all the details of your problem. I'm just doing my generic spiel πŸ˜‹

didibus20:11:12

Like personally, though it could depend on more context, but I feel @ example could have just been defined using functions

didibus20:11:01

What he has now feels very OOP to me

donaldball20:11:08

I have been driven down the macro inheritance road before and it was not a fun journey. I suggest extracting the common behavior to var fns and to call them as desired from the various protocol impls.

lennart.buit21:11:56

sounds reasonable

lilactown21:11:25

@ it was all in the effort to establish a basic notion of fetching data from an external data source with a cache. There ended up being multiple implementations of that based on the kind of data source we were using. e.g. we wanted to use GraphQL, so we dropped in apollo-client to the project to handle batching, caching, etc. and installed it using the Source protocol. Then any part of the application could read from it just like any other data source we had

didibus21:11:42

I really don't want to make implications without knowing the full context by the way, context is key for these design choices. In the end if it turned out well for you, that's all the proof needed.

didibus21:11:31

It does sound like the data source abstraction can be useful, if there are a lot of different ways to fetch data from different sources it totally make sense.

didibus21:11:55

As an alternative, just to show what I mean, you can create a map with a source-type and url and possibly more or less info. And just have a read and write multi-method over source-type.

lennart.buit21:11:01

Btw, extend is treating me well now

lennart.buit21:11:16

with some default function map ^^

didibus21:11:27

Ya, one thing coming from inheritance based languages is you get used to using inheritance as a tool for code reuse. And in Clojure code reuse is almost always just done by sharing the same function.

lennart.buit21:11:38

yeah is nice because I can now have multiple default method sets

didibus21:11:52

And that works well in Clojure because the dynamic types make it so all functions are generic in nature. So duck typing applies, as long as you give it compatible things it'll just work.

lennart.buit20:11:05

Otherwise, I may just write a multimethod deriving that does basically just that

peter.kehl20:11:57

Hello. What are the most common libraries with an HTTP client and to parse JSON, please? Ideally ones that work with deps.edn, too.

didibus21:11:41

Cheshire is good for json

didibus21:11:37

Is good for am http client

ghadi20:11:23

clj-http works well @peter.kehl

peter.kehl21:11:09

Thank you @ghadi