? stateful-map ?

142 views
Skip to first unread message

Jules

unread,
Nov 27, 2022, 2:00:48 PM11/27/22
to Clojure
Guys,

I've found myself needing a function that I am sure cannot be an original but I'm not aware of it existing anywhere...

It is a cross between 'map', 'reduce' and 'iterate'...

Given a function 'f' and a sequence 's' it would return you  a sequence of :
```
[(f s[0]) (f (f s[0]) s[1]) (f (f (f s[0]) s[1]) s[2]) ...]
```

or, more concretely, e.g.:
```
util-test> (stateful-map + [0 1 2 3 4 5])
[0 1 3 6 10 15]
util-test> 
```

I have a couple of approaches to it - one using reduce:

```
(defn stateful-map-1 [f [h & t]]
  (reduce
   (fn [acc v]
     (conj acc (f (last acc) v)))
   [h]
   t))
```

and another, mapping using a stateful function:

```
(let [secret (Object.)]
  (defn stateful-mapper [f]
    (let [state (volatile! secret)] (fn [v] (vswap! state (fn [old new] (if (= secret old) (f new) (f old new))) v)))))

(defn stateful-map-2 [f s]
  (mapv (stateful-mapper f) s))
```

The former feels more idiomatic whereas the latter (although uglier) is more efficient and has the added benefit of being able to be used for general map-ing which is important as I want to use this approach to transduce a clojure.async.channel.

It could, of course, be expressed directly as a transducer but it feels like something simpler that should only be lifted to a transducer as and when needed (see test below) ...

Here is my working testsuite:

```
(deftest stateful-map-test
  (testing "reduction"
    (is
     (=
      [0 1 3 6 10 15]
      (stateful-map-1 + [0 1 2 3 4 5]))))
  (testing "mapping stateful function"
    (is
     (=
      [0 1 3 6 10 15]
      (stateful-map-2 + [0 1 2 3 4 5]))))
  (testing "transduction"
    (is
     (=
      [0 1 3 6 10 15]
      (sequence (map (stateful-mapper +)) [0 1 2 3 4 5])))))
```

Am I missing a standard way of doing this in Clojure ? Or is a stateful function the best answer ?

Interested in your thoughts,


Jules

Jules

unread,
Nov 27, 2022, 2:19:45 PM11/27/22
to Clojure
I thought I should have a look at clojure.core.reducers in case there was an answer there - but this all still looks eager whereas I need something like a lazy reduction which applies a given function to its accumulator and each input to give it's new accumulator, which is output on each step...

Jules

Sam Ritchie

unread,
Nov 27, 2022, 6:47:22 PM11/27/22
to clo...@googlegroups.com
Pretty sure what you’re looking for is either https://clojuredocs.org/clojure.core/reductions or something close. This idea is called a “scanLeft” in some functional languages, so that should give you another search term to use. Good luck!
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

Jules

unread,
Nov 28, 2022, 7:51:58 AM11/28/22
to Clojure
Very nearly a direct hit :-)

'reductions' definitely gives me what I want when dealing with eager and lazy sequences ... but... it seems to have escaped the transducer makeover :-(

core> (reductions + 0 [1 2 3 4 5])
(0 1 3 6 10 15)
core> (take 6 (reductions + (range)))
(0 1 3 6 10 15)
core> (reductions + 0)
Error printing return value (IllegalArgumentException) at clojure.lang.RT/seqFrom (RT.java:557).
Don't know how to create ISeq from: java.lang.Long
core> (reductions +)
Execution error (ArityException) at theremin.core/eval351732 (form-init6069347464735150015.clj:60).
Wrong number of args (1) passed to: clojure.core/reductions
core> 

at least I now know what to call what I am looking for.

Thanks for your help - much appreciated,


Jules

Peter Strömberg

unread,
Dec 6, 2022, 3:56:22 AM12/6/22
to clo...@googlegroups.com
I just had a conversation with ChatGPT about this. It was quite long, but a point we were here:

image.png

Feeding that to the Joyride REPL however:

image.png

So then:

image.png

And back at the REPL:

image.png

😀

Steve Miner

unread,
Dec 9, 2022, 2:29:26 PM12/9/22
to clo...@googlegroups.com
See also https://github.com/cgrand/xforms for a transducer version of reductions. The argument f must have a nullary arity. (You could probably create your own variant if you want an explicit init value.) I think something like this should work:

(require '[net.cgrand.xforms :as x])

(defn xscan-left
([f coll] (sequence (xscan-left f) coll))
([f] (comp (x/reductions f) (drop 1))))

Reply all
Reply to author
Forward
0 new messages