Fork me on GitHub
#beginners
<
2022-02-03
>
bad_ash01:02:51

hi, i'm working on a simple website using Luminus and reframe. i'm having trouble figuring out how to retrieve entries inside a database from my backend and display them as a list on the frontend. in the luminus tutorial i read, it's done by passing the results of a database call to a selmer/render-file call, which in turn allows the template file specified in the render-file call to access those results. however, i'm not sure how to utilize this if i am using reframe to generate the frontend (which works by adding a script file generated by cljs to the template file) or if i'm supposed to use other means of retrieving those results. any help would be appreciated; thanks in advance

solf02:02:05

This is really a general question about web develop, nothing specific to clojure

solf02:02:14

When using re-frame (or, for example, react if you were writing in Javascript), you'll want to have an endpoint in your server that re-frame calls

solf02:02:43

And that endpoint would return the data read from the server, in json format (or edn, etc)

practicalli-johnny10:02:41

Another approach would be to have the backend expose the data as an API and a separate reagent/re-frame app for the front end.

bad_ash01:02:05

this is tangential to my question above, but i'm also rethinking about using Luminus to build a web application. i've come across this site: https://www.fullstackclojure.com/ which recommends using Compojure to create a web application, and i assume there are other viable options out there. how do you recommend i choose the proper means of creating a web application? i'm also curious about any general thoughts here.

seancorfield02:02:25

@ashbadine91 If you are just getting started with Clojure, I strongly recommend building a small server-side web app using just Ring and Compojure to gain an understanding of how Clojure deals with the web's HTTP request/response idiom. From there you can go on to build a simple REST-like API that allows a client (such as curl at the command line) to make requests and get data back as JSON. Then you can start in on the ClojureScript side of things and build a small client web app that makes those REST requests and deals with the JSON it gets back. Luminus bundles together a lot of moving parts and if you don't understand how the basics work, you'll flounder when trying to adapt a basic Luminus project to your needs. I do not recommend Luminus for beginners.

bad_ash00:02:35

thanks a lot for the project suggestions sean. it's good to get a sense of direction since i've felt fairly lost on how to get a website working using clojure

seancorfield01:02:37

Yeah, it can be a bit overwhelming because Clojure favors composing small libraries over the structured frameworks that many other languages have. It makes it much harder for beginners to get up and running in Clojure as far as web apps are concerned, but once you get used to it, assembling just what you need for any given solution is pretty straightforward.

bad_ash03:02:55

Are there any books/resources you would recommend for learning Clojure+ClojureScript as a supplement to actually using the languages? I only read https://www.learn-clojurescript.com/, which I thought would be enough but now I'm having second thoughts.

seancorfield05:02:23

I don't know much about ClojureScript resources, but I generally recommend Getting Clojure and Clojure Applied as two solid books for the Clojure side of things. There's a long list here https://clojure.org/community/books but my personal advice is to stick to books from Pragmatic primarily, with maybe some from O'Reilly and/or Manning, and avoid Packt.

bad_ash02:02:38

i'll take a look at those, thanks

seancorfield02:02:17

If you want to look at a small, database-backed, server-side web app that uses several libraries but is aimed at beginners: https://github.com/seancorfield/usermanager-example/ -- the README links to a variant built with Integrant and Reitit (instead of Component and Compojure).

seancorfield02:02:55

That variant, in turn, links to a version with a SPA front end, using Inertia.js and some middleware (I have no experience with that). At some point, I will get back to cljs and build a version of that usermanager example that has a re-frame front end and an API built into the backend. Some day...

2
John Bradens07:02:22

Ok so I'm trying to use prismatic/dommy just to learn how to use clojure libraries. I am following the learn-clojurescript book. One example has

(gdom/setProperties password #js {"type" "password"})
How would I write this using dommy? I required dommy.core :as dommy. I tried
(dommy/set-style! password :type :password)
I can't get the formatting write for this and idk where to find an example.

John Bradens07:02:00

I have

(let [password (dommy/create-element :input)
Can I make it a "password type" here? I want it to show up as black dots when typed 😕 And also just learn how to translate between doing the same things in different libraries & how to figure out the correct formatting

John Bradens07:02:52

Ok I figured out I do

(dommy/set-attr! password :type "password")
and it worked!

roelof13:02:04

Can someone explain this to me

(ns toy-project1.subs
  (:require
   [re-frame.core :as re-frame]
   [clj-time.coerce :as timec]))

(re-frame/reg-sub
 ::text
 (fn [db] (timec/from-long (get-in [:current :dt] db))))
Error :
[:app] Configuring build.
[:app] Compiling ...
[:app] Build failure:
The required namespace "clj-time.coerce" is not available, it was required by "toy_project1/subs.cljs".
"clj_time/coerce.clj" was found on the classpath. Maybe this library only supports CLJ?

Rupert (All Street)13:02:41

Have you added clj-time to you lein project.clj or deps.edn?

roelof13:02:53

yes

{:source-paths
 ["src"]

 :dependencies
 [
     [reagent "1.1.0"]
     [re-frame "1.2.0"]
     [cljs-ajax "0.8.4"]
     [day8.re-frame/http-fx "0.2.4"]
     [clj-time  "0.15.2"]
 ]

 :dev-http {8280 "resources/public"}

 :builds
 {:app {:target :browser
        :output-dir "resources/public/js/compiled"
        :asset-path "js/compiled"
        :modules
         {:app {:init-fn toy-project1.core/init}}
         }}}

roelof13:02:12

this is my shadow-cljs.edn

Sampo Toiva13:02:34

@U0EGWJE3E clj-time is a wrapper for Joda Time and is meant for Clojure side of things. You'd want to take a look at cljs-time.

Sampo Toiva13:02:35

Moreover, clj-time is deprecated. See here: https://github.com/clj-time/clj-time

roelof13:02:28

Oke, I changed it to this :

[com.andrewmcveigh/cljs-time "0.5.2"]

roelof13:02:00

and the code to this

(ns toy-project1.subs
  (:require
   [re-frame.core :as re-frame]
   [cljs-time.coerce :as timec]))

(re-frame/reg-sub
 ::text
 (fn [db] (timec/from-long (get-in [:current :dt] db))))

roelof13:02:26

but still this error :

:app] Compiling ...
[:app] Build failure:
The required namespace "cljs-time.coerce" is not available, it was required by "toy_project1/subs.cljs".

Sampo Toiva14:02:38

Have you restarted your repl? And actually, things have changed a bit since I last looked at this. Cljs-time is a bit old library in itself, so something else might be better. Something like https://github.com/juxt/tick or direct usage of Google Closure library goog.date might work better.

roelof14:02:39

I did not restarted it , trying it now

roelof14:02:26

have to re-think some things right now

roelof14:02:33

I do not see the time

Sampo Toiva14:02:23

@U0EGWJE3E what do you mean with you do not see the time? The code above isn't showing the time. It registers a subscriber for extracting data from the app database that your views can then use.

roelof14:02:48

I mean this

roelof14:02:06

i have this subscription :

(ns toy-project1.subs
  (:require
   [re-frame.core :as re-frame]
   [cljs-time.coerce :as timec]))

(re-frame/reg-sub
 ::time
 (fn [db] (timec/from-long (get-in [:current :dt] db))))

roelof14:02:51

and this in my template

(ns toy-project1.views
  (:require
   [re-frame.core :as re-frame]
   [toy-project1.subs :as subs]
   [toy-project1.events :as events]
   ))

(defn main-panel []
   (let[text (re-frame/subscribe [::subs/time])]
   [:div 
    [:div
         [:button {:on-click #(re-frame/dispatch[::events/fetch-weather])} "Get weather data "]]
   [:div text]]))
and I see the button but not the time

roelof14:02:53

there I see this : #object[reagent.ratom.Reaction {:val nil}]

Sampo Toiva14:02:55

You need to deref the subscription with @(re-frame/subscribe [::subs/time])

roelof14:02:02

and not the time

Sampo Toiva14:02:25

That will get you the value stored in the ratom. However, in your case that data is nil. There is no time.

roelof14:02:24

hmm, again back to the drawing table

roelof14:02:25

I know that this is the returned json which is converted to a vector

{
  "lat": 33.44,
  "lon": -94.04,
  "timezone": "America/Chicago",
  "timezone_offset": -21600,
  "current": {
    "dt": 1618317040,
    "sunrise": 1618282134,

Sampo Toiva14:02:58

So, somewhere, you need to initialize that data into the database. Moreover, you might wanna take a look at re-frame documentation. The layer 2 subscribers should be just that, a data subscribers. The data parsing should happen in a computation function. I.e. something like this:

(re-frame/reg-sub
 ::time
 (fn [db _] (get-in [:current :dt] db))
 (fn [dt _ _] (timec/from-long dt)))

roelof14:02:51

and it looks im doing the same as you but then in one step

roelof14:02:08

(fn [db] (timec/from-long (get-in [:current :dt] db))))

Sampo Toiva14:02:08

Your data seems fine. In any case, check how and when you write the data into the database and remember to deref with @ .

Sampo Toiva14:02:10

Yes. The two steps are an optimization. The layer 2 query fns are rerun every time the data in app db changes. Because of this, we want them to be trivial. Just read and return data.

roelof14:02:02

Thanks, I will do some debugging to see what is wrong

roelof14:02:15

Right now I do not have a clue

lassemaatta15:02:17

(get-in [:current :dt] db) seems not quite right

roelof15:02:22

@U0178V2SLAY what do you then think it should be ?

lassemaatta15:02:49

(get-in db [:current :dt])

roelof15:02:09

hmm, then still no date to see

lassemaatta15:02:20

what does your ::events/fetch-weather event handler look like?

lassemaatta15:02:51

or rather how is the data written to the app-db when you receive it as json?

roelof15:02:02

(re-frame/reg-event-db
 ::http-fail
 (fn [db [_ key-path]]
   (assoc-in db key-path {})))

(re-frame/reg-event-db
 ::http-success
 (fn [db [_ key-path result]]
   (assoc-in db key-path result)))

(re-frame/reg-event-fx
 ::fetch-weather
 (fn [_ _]
   {:http-xhrio
    {:method :get
     :uri    ""
     :params {:lat   (:lat config/home)
              :lon   (:lon config/home)
              :units "imperial"
              :appid config/open-weather-api-key}
     :response-format (ajax/json-response-format {:keywords? true})
     :on-success      [::http-success [:weather]]
     :on-failure      [::http-fail [:weather]]}}))

lassemaatta15:02:31

perhaps the path you use with get-in should be [:weather :current :dt] ?

roelof15:02:49

Then I see this error :

Uncaught Error: Objects are not valid as a React child (found: object with keys {date}). If you meant to render a collection of children, use an array instead.

lassemaatta15:02:45

I think you're getting closer

roelof15:02:38

it do not feel like that

roelof15:02:32

i see this on my javascript console

lassemaatta16:02:53

I’m afk for a moment, I’ll give you a few tips later

roelof16:02:25

and do not hurry. this is a learning exercise

lassemaatta16:02:01

So I think your subscription is working now, or at least it's returning some kind of a datetime object. However, reagent doesn't know how to represent it in html. That's why you get a "Objects are not valid as a react child" error when you try to use it as [:div text].

lassemaatta16:02:32

So the next step is to turn that datetime object into something suitable, e.g. a string containging the date and time in some nice format

roelof16:02:50

yes, I read and it seems that text is a object and not a primitive

roelof16:02:12

so im now trying to see if I can see how the object looks like

roelof16:02:30

so I can make a string or whatever of it

lassemaatta16:02:31

cljs-time has a function called to-string, which offers one such way to represent the datetime as a string

lassemaatta16:02:21

so perhaps try what (fn [db] (timec/to-string (timec/from-long (get-in db [:current :dt]))))) does and if it helps

roelof16:02:26

oke, so I can do [:div (cljs-time text) ]])) ?

lassemaatta16:02:14

[:div (timec/to-string text)] would work also I guess, as long as you remember to require it as [cljs-time.coerce :as timec]

roelof16:02:55

hmm, I tried but no output and no error message 😞

roelof16:02:07

or do I have to restart the server ?

lassemaatta16:02:30

normally modifying the cljs code doesn't require restarting the server

roelof16:02:44

I thought also

roelof16:02:20

I had to do (fn [db] (timec/to-string (timec/from-long (get-in db [:weather :current :dt])))))

roelof16:02:43

but got as answer : 1970-01-20T00:38:26.266Z which I think makes no sense

lassemaatta16:02:28

well, at least we got something working 🙂

roelof16:02:58

the docs of the api says this : current.dt Current time, Unix, UTC

roelof16:02:58

so that is looking well

lassemaatta16:02:04

typically the epoch (starting time) of various time systems is midnight 1.1.1970

roelof16:02:55

I know but the docs stated current time and for me it is the time right now

lassemaatta16:02:32

when you work with timestamps and see a value that's close to 1.1.1970 but perhaps a few days later -> somewhere there's a mixup between "seconds after 1.1.1970" and "milliseconds after 1.1.1970"

lassemaatta16:02:00

from-long says ""Returns a DateTime instance in the UTC time zone corresponding to the given number of milliseconds after the Unix epoch.""

roelof16:02:17

and I wonder if I use the right conversion from a date object to a javascript object

lassemaatta16:02:32

but the value from your API looks like it's seconds after the epoc, not milliseconds

roelof16:02:10

oke, so we have to multiply with 1000 ?

lassemaatta16:02:12

so, what we need to do is convert that to ms -> (fn [db] (timec/to-string (timec/from-long (* 1000 (get-in db [:weather :current :dt]))))))

lassemaatta16:02:31

yeah, correct

roelof16:02:06

that looks better

roelof16:02:10

2022-02-03T16:44:48.000Z

roelof16:02:36

the only thing is that it is 17:44

roelof16:02:50

so the summer/winter is bugging us now

roelof16:02:03

and then I only want the time

roelof16:02:11

wait I did not use my real location

roelof16:02:52

nope, still the same problem

roelof16:02:25

or schould I also use this one : `

"timezone_offset": -21600,

lassemaatta16:02:28

working with time zones is a constant pain. I'm not very familiar with cljs-time so I don't know what kind of tools it provides. One possible hack to solve your problem: that API response you get contains a "timezone_offset": -21600, -field. I guess that's the offset in seconds between the UTC timestamp and the local date. Perhaps you could add that offset to the original timestamp (before you multiply it by a 1000).

roelof16:02:04

thanks, I take a break right now nothing is chancing

(fn [db] (timec/to-string (timec/from-long (- (get-in db [:weather :timezone-offset]) (* 1000 (get-in db [:weather :current :dt]))))))

roelof16:02:16

Thanks for all the help

Fra15:02:09

Hi, is there a way to use async/chan to implement a publish/subscriber model with multiple subscribers of a single event?

Alex Miller (Clojure team)15:02:06

have you looked at pub and sub ?

Fra15:02:35

awesome! thanks @alexmiller

Alex Miller (Clojure team)15:02:17

(also, feel free to ask questions in #core-async in the future)

👍 1
erre lin16:02:44

Hello, I'm not sure if this should be an SQL channel question since it is more relevant to the database connection: Basically, I'm using HugSQL, next.jdbc to interact with PostgreSQL, sth like this:

;; database 
(def db
  ;; dbtype and dbname are preferred to be drop-in
  ;; replacement for subprotocol and subname
  {:dbtype   "postgresql",
   :dbname   "name",
   :user     "user",
   :password "passwd"})


(def ds (jdbc/get-datasource db))

;; connection and CRUD
(hugsql/def-db-fns "path/to/orders.sql"
  {:adapter (next-adapter/hugsql-adapter-next-jdbc)})
Then I can run a fn, say, (add-an-order db {:order_num "xxxx", :order_note "yyyy"}) to insert a record into the database. My question is, when I run such fns (generated by HugSQL), they will try to connect to the database I defined. Is this a only-once connection that will be closed after the sql statement is done? Or every time a long-time connection will be created, stay there even after the fn finishes, and take some RAM until I find I'm running out ram? I think the connection is of the first type: short-time and closed once data gets inserted. Where could I find more info on this? Thank you for your help

seancorfield17:02:36

I'd suggest asking in #hugsql -- I'm not sure how it manages connections. The recommended best practice is to set up a connection pool (which next.jdbc supports directly, using HikariCP or c3p0) and then pass that around so that operations that do getConnection() and then close() are using a pool of cached connections instead of standing up a new connection to the DB every time.

seancorfield17:02:24

It's normal to set up a connection pool at the start of your program and leave it in place until your program shuts down @U02UW9X8DUG

erre lin05:02:22

Thank you @U04V70XH6. Really helpful advice. I'll try out the tips you mentioned.

erre lin13:02:20

Hi@U04V70XH6, I have a quick question regarding hikari-cp (a Clojure wrapper to HikariCP) and next.jdbc. I read next.jdbc 's doc and find it uses HikariCP directly without the wrapper. When I tried to use the wrapper, it creates a datasource using its own db-spec hash map, where next.jdbc's :dbtype is declared as :adapter . Other differences include :user (next.jdbc) vs :username (hiker-cp) and :dbname vs :database-name Passing a connection pool created by hikari-cp to next.jdbc/get-connection will throw me an error: Unknown dbtype:, and :classname not provided. I guess there is some collision. Since next.jdbc works with HikariCP well, I'll just drop the wrapper lib. But I'm curious if my guess is right. Wondering if you happen to know anything about this? https://github.com/tomekw/hikari-cp#usage

dabrazhe17:02:04

A map function makes a call to an external api with account ids. When I print the the structure [accountid, apiresult] it's printed separately, asynch. ie [account1 ""] [account2 ""] and then "apiresult1" "apiresult2" How can I group the output together?

noisesmith20:02:09

mapping is lazy and that's usually not what you want when interacting with and API or other side effects like printing what happens is that the data isn't calculated until the data structure is inspected by the repl print operation, so you get a weird interleaving of printing of debug info from your code and printing of data structure contents by the repl if your main goal is to see the account and result together, it's better to write your function so it joins the two into data, and then print the resulting data eg:

(defn get-api-result [input]
  {:request-data input
   :result (do-query input)})
instead of printing inside the get-api-result function, print what it returns

👍 1
1
dharrigan19:02:49

Is there a better way of doing this? (filter #(clojure.string/ends-with? full-url %) ["" ""]) The idea is to allow through a full-url, only if it ends with any of a matching whitelist domain. It could return true or false. I thought about doing apply, but couldn't quite get it to work.

Darin Douglass19:02:09

you could use something like https://github.com/lambdaisland/uri and parse the urls to make the intent more clear. otherwise, it reads find to me ¯\(ツ)

dharrigan19:02:38

Thank you - will check that out 🙂

dharrigan19:02:53

Ah regex 🙂 Then I would have two problems 😉

dharrigan19:02:49

Thank you, will have a read.

flowthing19:02:55

(filter #(clojure.string/ends-with? "" %) ["" ""])
;;=> ("")
Or did I misunderstand?

flowthing19:02:25

user=> (import '( URI))
java.net.URI
user=> (#{"" ""} (-> "" URI. .getHost))
nil
user=> (#{"" ""} (-> "" URI. .getHost))
""
Could just use java.net.URI maybe?

dharrigan19:02:28

All great stuff, reading.

dharrigan19:02:19

Ah the problem with your approach @U4ZDX466T is that it'll fail with or (basically, the host part should be ignored):

dharrigan19:02:26

(#{"" ""} (-> "" URI. .getHost))

dharrigan19:02:20

However, this `(filter #(clojure.string/ends-with? "http://evil.com/foo.com" %) ["http://foo.com" "http://bar.org"]) ;;=> ("http://foo.com")` is a valid problem with my approach too!

flowthing19:02:52

I mean, I don't think there's a secure solution to that requirement.

flowthing19:02:08

(clojure.string/ends-with? "" "")
;;=> true

flowthing19:02:26

Or I can't think of one, at least.

dharrigan19:02:02

Yes, indeed. It's very tricky. I may just park it for now.

flowthing19:02:36

Depending on your situation, you could consider whitelisting CIDR blocks instead, or something like that.

dharrigan20:02:29

It's for a chat forum, so any user can post any link as text...danger will robinson danger!

dharrigan20:02:54

I'm going to go away and ponder...

noisesmith20:02:10

@U11EL3P9U or similarly bad "<http:evil-site.com#foo.com>"

noisesmith20:02:55

you can literally put anything after # and the browser happily ignores it if there's no match on the target page

noisesmith20:02:34

edit: URI is safe to use, URL does a network query for hash-code / equality checks

John Bradens20:02:47

Hi I'm doing the learn-clojurescript book. In one lesson we make a weather conversion widget. It says to try adding a button that will reset the input-box where you type in the tempurature to be converted. I'm trying to do something like

(defn reset-temp (gdom/setTextContent temp-input "")) 
and
(gevents/listen reset-button "click" reset-temp)

John Bradens20:02:06

And it's not working. Any ideas what I am doing wrong?

dpsutton21:02:30

that’s an invalid way to specify a function. Do you see any compilation warnings?

dpsutton21:02:46

(defn reset-temp [] (gdom/setTextContent temp-input ""))

John Bradens21:02:43

Oh I see. I have

(defn reset-temp [_] (gdom/setTextContent temp-input ""))

John Bradens21:02:48

I didn't copy it over correctly

John Bradens21:02:14

In index.html I have

<div>
        <input type="reset" id="reset-button">
      </div>

John Bradens21:02:31

Then this is my whole file:

John Bradens21:02:59

(ns learn-cljs.temp-converter
  (:require
   [goog.dom :as gdom]
   [goog.events :as gevents]))

(defn f->c [deg-f]
  (/ (- deg-f 32) 1.8))

(defn c->f [deg-c]
  (+ (* deg-c 1.8) 32))

(def celsius-radio (gdom/getElement "unit-c"))
(def fahrenheit-radio (gdom/getElement "unit-f"))
(def temp-input (gdom/getElement "temp"))
(def output-target (gdom/getElement "temp-out"))
(def output-unit-target (gdom/getElement "unit-out"))
(def reset-button (gdom/getElement "reset-button"))

(defn get-input-unit []
  (if (.-checked celsius-radio)
    :celsius
    :fahrenheit))

(defn get-input-temp []
  (js/parseInt (.-value temp-input)))

(defn set-output-temp [temp]
  (gdom/setTextContent output-target
                       (.toFixed temp 2)))

;; 5 event handling callback 
(defn update-output [_]
  (if (= :celsius (get-input-unit))
    (do (set-output-temp (c->f (get-input-temp)))
        (gdom/setTextContent output-unit-target "F"))
    (do (set-output-temp (f->c (get-input-temp)))
        (gdom/setTextContent output-unit-target "C"))))

(defn reset-temp [_]
   (gdom/setTextContent temp-input ""))

;; 6 attach event handlers 
(gevents/listen temp-input "keyup" update-output)
(gevents/listen celsius-radio "click" update-output)
(gevents/listen fahrenheit-radio "click" update-output)
(gevents/listen reset-button "click" reset-temp)

Filip Strajnar20:02:49

I wish to encrypt files with AES (256 bit key, CBC mode) using streams (more specifically file streams), and i don't wish to reinvent the wheel so i'm wondering if there is an existing library that'd allow me to do that?

mbjarland22:02:19

I’d like to perform the job of a reduce which produces a sequence. In this case where each element in the resulting sequence depends in some arbitrary way on the previous element in the resulting sequence and an element in the reduced over collection. I can do this with reduce, no problem, but I would like to do this lazily, i.e. so that the calculation for a specific element in the resulting collection is only performed on request. Any thoughts on how to accomplish that? This is still kind of on the boiler in my head so I might be foobaring something in my thinking…

phronmophobic22:02:38

A common idiom that I use is:

(map (fn [prev current] (f prev current))
   xs
   (rest xs))

phronmophobic22:02:34

one note is that it will only call the mapping function for every element that has a previous. If you want to call it for every value, you can do something like:

phronmophobic22:02:24

(map (fn [prev current] (prn prev current))
     xs
     (concat (rest xs) [nil]))

mbjarland22:02:50

hmm…this was to replace something like:

(reduce 
  (fn [[a p] v] [(conj a (operation p v)) v])
  [[] nil]
  coll)
i.e. a reduce which reduces over a collection, but also depends on the previous value of the resulting collection (`a` above)

mbjarland22:02:32

hmm…seems I can probably do this somehow via reductions…ok think I have enough to keep digging. Thanks for the pointers!

👍 1
phronmophobic22:02:05

> i.e. so that the calculation for a specific element in the resulting collection is only performed on request as a side note, if you want to avoid doing any unnecessary work reduction, then lazy sequences can sometimes get you into trouble due to chunking. If doing a little extra work isn't an issue, then the lazy sequence versions should work fine. If it is an issue, you may want to explicitly apply the reducing function yourself on request.

noisesmith23:02:07

(reduce (fn [acc [prev current]]
          (conj acc (operation prev current)))
        init
        (partition 2 1 coll))
or what you had already with the composit accumulator destructure

noisesmith23:02:49

(defn foo
  [op init coll]
  (if (< (count coll) 2)
    ()
    (lazy-seq
      (let [x (apply op (take 2 coll))
            acc (conj init x)]
        (cons acc
              (foo op acc (rest coll))))))
lazy-seq version, but I think you only want the last value so reduce is better

noisesmith23:02:40

and I guess reductions is still better if you need the transitory values in between - lazy-seq requires too much sequence walking boilerplate so it's much less clear

Ben Sless07:02:43

There's also a reductions transducer in the xforms library, then you wont have to worry about chunking

mbjarland09:02:17

thank you all for the input. Specifically for this particular case I was looking to calculate various trend metrics for finance. Exponential moving average is an example, i.e. you get a coll/seq in, you produce a new coll/seq as output based on a calculation which depends on current element and last calculated value. For ema specifically it seems reductions is beautiful:

(defn ema [^long n coll]
  (let [a (/ 2 (inc n))]
    (reductions
      (fn [pv v] (+ (* a v) (* (- 1 a) pv)))
      (first coll)
      (rest coll))))

(ema 6 (range 5))
=> (0 2/7 38/49 484/343 5164/2401)
I’ve run across reductions a few times and never really saw the light…clojure keeps expanding the mind.

mbjarland09:02:22

where the first argument is the length of the coll, I do this as my actual function has a transducer arity and I think the transducer needs to be told the length of the input as it can not do “global” operations on the indata

mbjarland09:02:18

anyway…thanks @U051SS2EU, @U7RJTCH6J and @UK0810AQ2! Will hold on to your notes as I’m sure I will have use for them over the course of this project.

noisesmith21:02:32

@UK0810AQ2 when using lazy-seq directly I don't think chunking comes into play? it's higher level stuff like map that respects chunks I thought

noisesmith21:02:53

yeah - no worry about chunks when using lazy-seq directly

(cmd)user=> (defn lazy-map
              [f coll]
              (lazy-seq
                (when (seq coll)
                  (cons (f (first coll))
                        (lazy-map f (rest coll))))))
#'user/lazy-map
(ins)user=> (take 1 (lazy-map println [1 2 3]))
(1
nil)
(cmd)user=> (take 1 (map println [1 2 3]))
(1
2
3
nil)

mbjarland22:02:12

perhaps some application of lazy-seq?