Fork me on GitHub
#beginners
<
2020-03-03
>
heatonsam03:03:35

Hello again, this is probably a really dumb question. I know map is such a common function in Clojure but I'm having trouble wrapping my head around it or something. I have a function akin to this:

(defn update-thing
  [my-maps]
  (map
   (fn
    [x]
     map #(update x (key %) (str (val %) "somethingelse")) x)
   my-maps))
And essentially this part (str (val %) "somethingelse") is doing nothing. My expectation is that the inner map function will be called on the seq that is the product of the outer map function but I think I'm misunderstanding. Put simply for each hash-map in my-maps I want to replace the value of each element in the hash-map with something else that's based on the existing value. For my example I just put (str (val %) "somethingelse") as something arbitrary. However I just get back the same values as the original my-maps.

seancorfield03:03:42

@heatonsam The function that you're mapping over my-maps isn't doing anything useful

seancorfield03:03:52

(fn [x]
map ; evaluates to the function and is then ignored
#(update x (key %) (str (val %) "somethingelse")) ; evaluates to a function and is then ignored
x) ; returns the input value x

seancorfield03:03:29

First off, are you trying to map a function over a sequence of sequences, that would need you to map-over-`map`?

heatonsam03:03:32

ugh, I am missing parens again around the inner map, I see that now.

seancorfield03:03:15

my-maps is what? A sequence of hash maps? In which case you don't want map-over-`map` -- just a single map over the sequence is enough.

heatonsam03:03:52

it's a sequence of hash-maps yes

seancorfield03:03:27

And what are you trying to do to each hash map?

heatonsam03:03:07

I want to replace each value in each hash-map with a new value that includes the previous value, for example's sake a string concatenation like above

seancorfield03:03:49

OK, so you want to figure out the function that is applied to each hash map -- and then just call map using that over the sequence.

heatonsam03:03:18

That headline seems to describe my mistake exactly

seancorfield03:03:32

And you want to replace every value in each hash map?

heatonsam03:03:52

yes, which is why I thought I should map over them again

seancorfield03:03:06

Probably the easiest way to process a hash map and apply a function to each key and/or value is reduce-kv

seancorfield03:03:43

(reduce-kv (fn [m k v] (assoc m k (str v "somethingelse"))) {} a-hash-map)

seancorfield03:03:59

That will process a single hash map and replace every value.

seancorfield03:03:38

Then you just

(map #(reduce-kv (fn [m k v] (assoc m k (str v "somethingelse"))) {} %) my-maps)

heatonsam03:03:09

that's much cleaner

heatonsam03:03:15

I will read Stuart's article as well.

heatonsam03:03:59

worked perfectly

seancorfield03:03:00

If you do find yourself wanting to treat a hash map as a sequence of MapEntry pairs, you can also do

(into {} (map (fn [[k v]] [k (str v "somethingelse")]) my-maps))
using destructuring to separate the key and the value, or the transducer version (which is very similar but is easier to compose)
(into {} (map (fn [[k v]] [k (str v "somethingelse")])) my-maps)

tjb03:03:51

hey team! im super confused by this

user=> 
(assoc-in {} [:id] (str (java.util.UUID/randomUUID)))
{:id "ee01c6a7-7692-485e-b125-a76a7908a993"}
user=> 
(defn uuid [] (str (java.util.UUID/randomUUID)))
#'user/uuid
user=> 
(assoc-in {} [:id] uuid)
{:id #object[user$uuid 0x6a244dbe "[email protected]"]}
user=> 

tjb03:03:03

when i call uuid by does it show and object and not the value?

seancorfield03:03:50

You are not calling uuid, you're just using the function name.

tjb03:03:59

πŸ€”

seancorfield03:03:59

(uuid) would be calling it.

tjb03:03:41

thanks sean

seancorfield03:03:46

NP. These things often take a new set of eyes.

tjb04:03:05

ok another one. last one i swear! why would a json include a namespace?

{
    "patents/name": "Matt's Workout",
    "patents/id": "8fa24c8d-2f78-47cc-9e46-bc339705dadb"
}

tjb04:03:23

vs

{
    "name": "Matt's Workout",
    "id": "8fa24c8d-2f78-47cc-9e46-bc339705dadb"
}

andy.fingerhut04:03:30

Was the json file created by a call you made to a Clojure function that writes it? Or did someone else provide it to you?

tjb04:03:50

yeah im calling an api endpoint

tjb04:03:00

which uses clojure/ring/compojure

andy.fingerhut04:03:22

If you created it from the contents of a Clojure map, perhaps the keys in one or more of your maps have namespaces in the keys?

andy.fingerhut04:03:56

e.g. {:patents/name "Matt's Workout"}

tjb04:03:17

i printed it out prior to saving it in the database and i get this

tjb04:03:19

{name Matt's Workout, :id #uuid "8fa24c8d-2f78-47cc-9e46-bc339705dadb"}

tjb04:03:32

i wonder if the return from postgres is adding it?

andy.fingerhut04:03:31

seems possible -- I believe several Clojure libraries for dealing with databases have options to include/exclude such things.

tjb04:03:47

ok ill read up more on jdbc next

andy.fingerhut04:03:13

and/or some existing code you are using (perhaps developed by others?) is doing that custom on its own.

tjb04:03:04

i dont think it is that since i wrote everything haha πŸ™‚ i think it is my inexperience with clojure and completely understanding it. ill dig more, thanks for helping me rubber duck andy!

tjb04:03:26

oh it totally is the table name

tjb04:03:33

patents that is being prefixed

dpsutton04:03:38

i think next.jdbc does this by default

tjb04:03:54

interesting! i wonder why it would be the default

tjb04:03:54

awesome thank you dan! i am reading now

tjb04:03:34

boom got it! thanks again dan

tjb04:03:48

(defn create
  [db post]
  (let [to-save (assoc-in post [:id] (repo-util/uuid))]
    (sql/insert! db :patents to-save {:builder-fn result-set/as-unqualified-maps})))

seancorfield04:03:44

@tjb Be aware that only PostgreSQL returns the whole newly inserted row. Other databases just return some representation of a newly inserted key from insert!.

franklineapiyo05:03:09

If you have a full stack app; as in, one with a back end and a front end. Is it advisable to separate out you code base's dependencies such that the clojurescript dependencies are in one project.clj file and the clojure dependencies are in a different project.clj file depending on the build tool you are using.

franklineapiyo05:03:16

any thoughts from anyone?

franklineapiyo05:03:12

because when they are mixed up, sometimes it's hard to use lumo or Mike Fikes's plank

seancorfield05:03:28

@franklineapiyo One of the benefits of using Clojure is that you can share code between the front and back end using .cljc files so it's more common to have a single project containing both front and back end code in my experience.

seancorfield05:03:55

Most of the full stack example apps I've seen have a single project control file in a single repo. That said, for large applications, I can imagine a front/back end separation (but I'm not sure what folks would do with shared code -- maybe have a third project and build sharable artifacts?).

seancorfield05:03:38

(at work we use a monorepo anyway and CLI/`deps.edn` to manage each subproject -- but we don't use ClojureScript)

franklineapiyo05:03:03

@seancorfield thanks for answering, so if you guys don't use clojurescript, do you use figwheel?

seancorfield05:03:00

Nope. We looked at cljs years ago and it wasn't ready for prime time so we used JS for our front end. If we were starting over, we might reconsider that. I think, right now, I'd look at shadow-cljs first.

franklineapiyo05:03:05

mmh... thanks, I'll take a look at that simple_smile

franklineapiyo05:03:35

@seancorfield, your awsome.

seancorfield05:03:46

I keep meaning to take a more serious look at cljs. We've switched to the CLI/`deps.edn` at work (we switched from lein to boot back in 2015), so I'd have to figure out what's the best fit with the CLI tooling.

sindhurik62007:03:04

πŸ‘‹ I’m here! What’d I miss?

edward.hughes191109:03:31

I think I went and confused myself. An implicit do in a defn would reach down through an if and evaluate the forms in whatever branch the switch evaluates to in order, correct?

hindol.adhya09:03:56

I am a little confused by the wording "through an if".

(defn f
  [x]
  (if (pred x)
    true
    false)
  (prn x)) ; <= this will execute

hindol.adhya09:03:11

But in the if there are no implicit dos. An if can take one predicate and exactly 2 forms (actually, you can skip the else part too).

edward.hughes191109:03:22

I meant if I had an if that needs to branch out into two separate blocks of procedures, but yeah, thinking about it now I need to wrap it in a do so if doesn't complain about arity.

penryu09:03:42

Yep. If either branch is more than one form, it needs do or similar form (eg, let) to wrap them.

penryu09:03:03

(if (foo-p x)
  ;; true branch
  (let [c (bar x)]
    (println x "is foo-p-positive")
    (baz c))
  ;; false branch
  (do (thing-1)
      (thing-2)))

teodorlu10:03:42

Two alterantives to if with "implicit do" are when and when-not.

(when true
   (allways-running-1)
   (allways-running-2)
   (allways-running-3))

hindol.adhya11:03:10

Yeah, but with when there is no else part. If you need both branches, if with do is arguably better than using when and when-not one after the other.

hindol.adhya11:03:18

However, if you have only one branch instead of two, if without the else part is sometimes considered bad (for example, clj-kondo will flag it). In that case, when is preferred.

altjsus09:03:32

Good day! I came to Clojure from Haskell. Please help me understand the following notation:

(defn select
  [{:keys [api-key] :as options}] ..._

altjsus09:03:53

What {:keys [api-key] :as options} means

ahmed1hsn09:03:24

It's called Destructuring.

edward.hughes191109:03:55

You're telling the function that the argument is a map with a keyword/value pair with api-key as the keyword (plus whatever else is in the vector), and that you want to bind that value to the name options .

edward.hughes191109:03:28

Destructuring is a good way to pull in a complex data structure in its entirety to a function and pick out what you need as you need it.

altjsus09:03:14

Thnx. I tried different searches cos I thought :as and :keys are keywords.

altjsus09:03:21

In given case.

edward.hughes191109:03:47

well, they are, strictly speaking, they're just doing something specific here.

edward.hughes191109:03:31

But yeah, any time you see a map inside an argument vector and that sort of structure inside it, you should be thinking of destructing a data object.

penryu10:03:19

Destructuring is a little bit more magic than I usually attribute to Clojure, so studying the various forms of destructuring was the best way for me to recognize them when I see them.

timur05810:03:07

@ It's equivalent to this: (defn select [options] (let [api-key (get options :api-key)] ... ))

teodorlu11:03:16

Example:

user=> (let [{:keys [api-key] :as options}
             {:api-key "secret" :other 99}]
         [api-key options])
["secret" {:api-key "secret", :other 99}]

teodorlu11:03:40

Principle: you can put a map where you would normally put a symbol. Instead of binding to the symbol, you do funny destructuring stuff based on a map-based syntax.

teodorlu11:03:44

You can use destructuring in defn , let and a few other places where assignment makes sense.

altjsus16:03:17

Thanks a lot for your explanations. What a nice community.

jehaby13:03:04

Hi! Is it possible to do a sequential destructuring inside function args in this example:

(defn f [sum letters] ; expects int and vector of two elements
       (let [[a b] letters] (if (= a b) sum (inc sum))))
something like
(defn f2 [sum [a b] letters]
       (if (= a b) sum (inc sum)))
f2 doesn't work like I want, because it expects 3 args.

jehaby13:03:32

figured it out πŸ™‚

(fn [sum [a b]] (if (= a b) sum (inc sum)))

stebokas15:03:18

Why

(defmacro zed [] `(println "----" ~(meta &form)))
(zed)
this evaluates correctly, but this
(defmacro zed [] `(println "----" ~(identity &form)))
(zed)
does not? πŸ€” I just want to see what is available in the &form var.

alexmiller16:03:13

because &form is (zed)

alexmiller16:03:41

so the macroexpansion of (zed) includes (zed), which is macroexpanded to (zed) which is macroexpaned to (zed) ...

alexmiller16:03:43

stackoverflow

alexmiller16:03:56

user=> (defmacro zed [] `(println "----" ~(str &form)))
#'user/zed
user=> (zed)
---- (zed)

stebokas16:03:22

ok. I understood πŸ™‚ so from ~(meta &form) I can have line and column. Is it possible to get or somehow infere the calling function name?

alexmiller16:03:00

you mean who is calling zed?

heatonsam16:03:53

Another similar question to yesterday trying to figure out map and reduce. The function below works, but instead of updating just one entry (`:value`) I want it to take a vector of keywords and apply update to all of them. I know I need reduce for this but my attempts have failed. Can anyone suggest how I could call reduce within map here? (defn parsed-csv [csv-data] (let [data (csv-data->maps csv-data)] (map (fn [csv-record] (update csv-record :value #(Integer/parseInt %))) data)))

heatonsam16:03:05

Trying this for instance just returns a function: (defn parsed-csv [csv-data] (let [data (csv-data->maps csv-data)] (map (fn [csv-record] (reduce (fn [x] (update csv-record x #(Integer/parseInt %))) csv-record) data))))

heatonsam16:03:00

I'm so used to imperative programming where I would just use loops for everything

doby16216:03:19

Are you expecting a single value or a collection as a return value from this function? Or will you run this function in order for side effects to do something like update a csv db record?

heatonsam16:03:17

Expecting a seq of maps to come back. No side effects, this is just for parsing specific columns of a CSV file into ints.

heatonsam16:03:49

data is a seq of maps in this case

doby16216:03:13

so csv-record is a map then? Calling reduce on a map (collection) is going to convert it to a value.

heatonsam16:03:40

csv-record is a map yes

heatonsam16:03:37

ok, in that case then my approach was wrong. I just want to iterate over a seq of maps and apply a function (Integer/parseInt in this case) to the values associated with specific keys contained in a vector.

ghadi16:03:01

get your function working on a single map

ghadi16:03:16

then it's a matter of mapping that function over the seq of maps

doby16216:03:36

Is there any linguistic way to avoid using map as both a verb and a noun in basically all of my sentences? I feel like I end up saying the words "key value pair collection" a lot to not sound confusing.

doby16216:03:31

I'll try that one on for size

heatonsam16:03:54

still close but easier than "key value pair collection" πŸ™‚

ghadi16:03:27

sorry the words are overloaded

heatonsam16:03:30

ah ok, that's probably a good approach thank you

lepistane17:03:18

I want to create image with clojure. Simple avatar simulator. Random background color and initials (2 letters of username) saved to a file. I feel like this is easy and trivial but i haven't found any clj libraries for this kind of purpose. Are there any resources you could point me to? should i use java interop for this? which lib?

matheus.emm17:03:54

if you know java2d you’ll probably be able to do what you want with java interop.

heatonsam18:03:06

I've got a bit further but I'm pretty stuck conceptually. I have these two functions:

(defn value->int
  [m k]
  (update m k #(Integer/parseInt %)))

(defn values->int
  [m kws]
  (map #(value->int m %) kws))
The first is working fine for updating one value in a map based on the key. In the case of the second, I want to iterate over the keywords whose values should be parsed into ints. But of course since I'm not mapping over the hash-map but the vector of keywords, it's returning one hash-map for each element in the vector of keywords, with just one of the values parsed into ints each. I get why this is happening, but I don't understand the solution. Example:
(values->int {:decimals "0",
  :symbol "",
  :geo "Country",
  :value "16"} [:value :decimals])
returns:
({:decimals "0", :symbol "", :geo "Country", :value 16}
{:decimals 0, :symbol "", :geo "Country", :value "16"})
should return:
{:decimals 0, :symbol "", :geo "Country", :value 16}
In my typical imperative programming I would just do something like below, to illustrate what I'm trying to achieve:
for (keyword : Keywords) {
  hash-map[keyword] = Integer.parseInt(hash-map[keyword])
}

bfabry18:03:24

map is a function where the results of each application are returned as a sequence

bfabry18:03:44

reduce is a function, where the result of each application is fed into the next application

bfabry18:03:48

so use reduce! πŸ™‚

bfabry18:03:53

(reduce (fn [new-m k] (value->int new-m k)) m kws)

heatonsam18:03:55

wow :star-struck:

heatonsam18:03:18

I was trying to use reduce earlier but couldn't get the params right. thank you this is very cool

bfabry18:03:56

or as a recursive loop:

(loop [new-m m
       kws kws]
  (if (empty? kws)
    new-m
    (recur (value->int new-m (first kws)) (rest kws))) 

bfabry18:03:42

the function reductions can be helpful for understanding/debugging reduce. https://clojuredocs.org/clojure_core/clojure.core/reductions

heatonsam18:03:47

oh, that's very cool. thank you. I was trying to use reduce by basically substituting it for map when the syntax I can see should be a bit different

bfabry18:03:58

you're welcome

noisesmith18:03:10

btw (fn [new-m k] (value->int new-m k)) can be replaced with value->int

noisesmith19:03:56

so the big payoff: (reduce value->int m kws) - this is why we lambdalove clojure

heatonsam19:03:45

This is helping me refactor some of the code I wrote three weeks ago that now looks ghastly πŸ˜‚

deep-symmetry21:03:24

That was my experience for my first few months of using Clojure. The journey was well worth it!

penryu19:03:50

So I've often heard function composition (comp f g) vocalized as "`f` after g", but it seems like composition of transducers is the reverse. Is this by design? An artifact of implementation?

penryu19:03:59

Eg:

user=> (map (comp inc #(* % %)) (range 10))
(1 2 5 10 17 26 37 50 65 82)
user=> (transduce (comp (map inc) (map #(* % %))) conj (range 10))
[1 4 9 16 25 36 49 64 81 100]

ghadi19:03:24

@penryu ^

ghadi19:03:29

there's a section about that

penryu19:03:16

Ah, here it is: > Composition of the transformer runs right-to-left but builds a transformation stack that runs left-to-right (filtering happens before mapping in this example). Thanks, @ghadi

ghadi19:03:35

AKA comp isn't doing anything different

penryu19:03:58

Is there a discussion/write-up about how these transformation/composition mechanics work under the hood?

penryu19:03:36

I can hand-wave my own mind to accept it and use the (->> ... analogy, but would love a more detailed/nitty-gritty dive.

jaihindhreddy08:03:31

Rich Hickey explained transducers in his strange loop talk: https://www.youtube.com/watch?v=6mTbuzafcII And he goes into much more detail in "Inside Transducers" explaining the implementation of some of the transducer-returning fns in clojure.core including some stateful transducers: https://www.youtube.com/watch?v=4KqUvG8HPYo If you prefer text to video, there are transcripts of those talks available here: https://github.com/matthiasn/talk-transcripts/tree/master/Hickey_Rich But I'd definitely recommend watching the talks.

hiredman19:03:58

the tricky thing is a transducer is a function from a function to a function, so in use it is a two step process, you apply the transducer to the reducing function (when is when the comp'ed functions run, as usual, right to left), and the result is a reducing function wrapped in all the transducer transforms, where the last transform is the outer wrapper, and that reducing function is then used to actually reduce, and it runs outside to inside (or left to right)

bfabry19:03:35

so long as you can stand the python

penryu19:03:11

Hah. I can suffer through. Thanks, @bfabry.

hiredman19:03:13

the function you get out of comp, doesn't actually do all those things (mapping, filtering, etc) it transforms the reducing function to do those things, which means wrapping the reducing function, and when you apply wrappers the first wrapper applied is the innermost wrapper

hiredman19:03:43

you would observe a similar behavior if it was common practice to compose ring middleware that way

penryu19:03:17

As in, if you composed middlewares, and then applied them to handlers?

hiredman19:03:50

well, they are composed and applied, it just isn't common to use comp to do it

hiredman20:03:20

(def middleware (comp (fn [handler] (fn [req] (handler (update-in req [:stack] conj :c))))
                      (fn [handler] (fn [req] (handler (update-in req [:stack] conj :b))))
                      (fn [handler] (fn [req] (handler (update-in req [:stack] conj :a))))))

(def handler (middleware identity))

(handler {})

;;=> {:stack (:a :b :c)}

johnwesonga20:03:58

I have a vector with integer [3 4 9 10 12] and I need to iterate through the elements and multiply while decrementing the multiplier and finally add up all the elements once I'm done: 3*10 + 4*9 + 9*8 + 10*7 + 12*6 Any ideas?

aisamu20:03:04

If you're stuck, it can be useful to post what you've already got! map can go over two collections at once, pair-wise

aisamu20:03:16

and range can go downwards if you give it a -1 step

tzafrirben20:03:27

why not use loop with recur? you can decrement the multiplier and sum up numbers until vector is empty

tzafrirben20:03:58

(defn my-fn [nums]
  (loop [multiplier 10
         numbers    nums
         sum        0]
         (if-not (seq numbers)
           sum
           (recur (dec multiplier)
                  (rest numbers)
                  (+ sum (* multiplier (first numbers)))))))
There are probably other ways, maybe more efficient - but it should do the trick

noisesmith21:03:49

user=> (apply + (map * (range 10 1 -1) [3 4 9 10 12]))
280

johnwesonga22:03:57

The (range 10 1 -1) would stop at 2 though

johnwesonga23:03:23

I think you're solution is actually correct, I've tried it out with some vectors and the output is as expected

noisesmith01:03:49

right, it's just that it should extend to 1 for maximum generality

noisesmith01:03:05

who knows what's expected after 1...

johnwesonga18:03:17

(range 10 0 -1) solves the issue

doby16220:03:38

Sounds like you could use map-indexed and multiply the integer by, say, (- multiplier index), then use (reduce + data) to sum it all up

hindol.adhya20:03:29

What is a nice way of reading a graph from a file? The undirected graph is written as a 2D matrix in a file (adjacency matrix). Currently I have this,

(defn read-edges
  [reader]
  (let [rows    (into [] (->> reader
                              line-seq
                              (map #(str/split % #","))))]
    (into (sorted-set-by #(< (:weight %1) (:weight %2)))
          (apply concat
                 (map-indexed
                  (fn [i row]
                    (remove nil?
                            (map-indexed
                             (fn [j value]
                               (when (and (not= value "-")
                                          (< i j))
                                 {:weight   (Integer/parseInt value)
                                  :vertices #{i j}}))
                             row)))
                  rows)))))
Ignore the sorted-set bit. I kept the edges ordered by weight but that is not a necessity.

andy.fingerhut21:03:44

Looks pretty reasonable to me. There are libraries like Loom and Ubergraph that have a few algorithms on graphs implemented for them, e.g. connected components, depth/breadth-first search, shortest paths, etc., but they do not handle that particular file format, as far as I know, and are not necessary for you to use by any means. I mention them only in case you are looking to use an algorithm they already implement for you.

hindol.adhya21:03:16

I am solving a puzzle, so trying to implement an algorithm myself. But these libraries can be useful later.

hindol.adhya21:03:25

The problem with my approach is the nested map-indexed. I don't like how the inner map-indexed is using i from the outer map-indexed. It is preventing me from lifting the fns up into a letfn.

andy.fingerhut21:03:48

I mean, some people positively love that functions declared inside of other code like that have access to all values in their lexical environment, without having to pass them as args.

andy.fingerhut21:03:04

(pointing at myself there)

hindol.adhya21:03:47

πŸ™‚, Yeah, I like them too. But here the code has become a little cryptic. It is not easy to understand what is happening here.

andy.fingerhut21:03:15

You could try a for that begins something like (for [i (range n), j (range n)] ...)

andy.fingerhut21:03:16

OK, maybe not that code I suggested, but the idea of a for that has two 'loop variables', which is a way to get nested looping behavior but the code is not so nested.

hindol.adhya21:03:49

Simply put, I need to traverse a 2D array while maintaining current row (`i`) and column (`j`). There might be idioms somewhere.

hindol.adhya21:03:16

I will try to re-write with for and see how that looks.

bfabry21:03:42

if it's an actual array you can use amap btw

hindol.adhya21:03:01

No, I mean vector of vectors.

andy.fingerhut21:03:02

Maybe something like (for [[I row] (map-indexed (fn [I row] [I row]) rows), [j value] (map-indexed (fn [j val] [j val]) row) ...bodyhere...)

andy.fingerhut21:03:21

That looks better spread out over 2 lines, but still maybe not great

andy.fingerhut21:03:46

Those two fn expressions are actually the same function, and could be replaced with vector

hindol.adhya21:03:51

Actually, got an idea. If I at least pull out a defn map-indexed-2d, the code will become more modular.

andy.fingerhut21:03:37

(for [[i row] (map-indexed vector rows),Β [j value] (map-indexed vector row) ] ...bodyhere... )

andy.fingerhut21:03:12

but yes, if you can pull out a map-indexed-2d that sounds useful

hindol.adhya21:03:03

This last one with for also looks pretty neat.

tylertracey0922:03:04

I know this is a bit vague, but I was hoping for some guidance. I am currently beginning what will likely be a long journey in the process of understanding the real-time modeling of worlds. I am planning to start small, by creating a graph database and modeling some tiny subset of relationships found in our world. I am thinking of starting with scientific papers and their references to each other to build a historical web of papers and their influence on one another. I was wondering what technologies/cloud providers/etc. you all might use to accomplish. Ideally I would like to use clojure, and I definitely plan to introduce event sourcing at some point (current state of the world at any one point in time is the result of starting state + aggregation of events so far).

tylertracey0922:03:55

I came across Amazon Neptune and I was wondering if Clojure would be a good choice to interact with that service. If so, where should I start?

tjb22:03:53

for graphs i would highly recommend looking at Neo4j

tylertracey0923:03:28

With Neo4j wouldn't I have to manage infrastructure myself?

tylertracey0923:03:42

I'm pretty green when it comes to devops/infrastructure.