braveandtrue

https://www.braveclojure.com/
moo 2018-08-29T14:37:22.000100Z

Hi @manutter51 good morning. 🙂

moo 2018-08-29T14:37:35.000100Z

So, I figured out how to get the end of a seq for my comp

moo 2018-08-29T14:37:37.000100Z

but…

moo 2018-08-29T14:39:06.000100Z

((fn [v] (let [[fn-k & rest] (rseq v)] (str fn-k))) ["a" "b" "c" "d"])
as a test works, and gives me the last fn however. I want to un-reverse that rest for the next recursive call and that isn’t working.
((fn [v] (let [[fn-k & rest] (rseq v)] (str (rseq rest)))) ["a" "b" "c" "d"])
     
; ==> ClassCastException clojure.lang.APersistentVector$RSeq cannot be cast to clojure.lang.Reversible  clojure.core/rseq (core.clj:1532)

moo 2018-08-29T14:39:54.000100Z

I’m thinking along the lines of this (but it doesn’t work yet

(defn my-comp
  ([f1 f2] (fn
            ([] (f1 (f2)))
            ([a1] (f1 (f2 a1)))
            ([a1 & rest] (f1 (apply f2 a1 rest)))))
  ([f1 f2 & morefns] (
                       let [[fn-k & rest] (rseq morefns)] (fn-k (apply my-comp f1 f2 (rseq rest))))))

moo 2018-08-29T14:41:27.000100Z

that last line should call (fn-k (apply my-comp ( reversed rest)))

2018-08-29T14:45:26.000100Z

Hmm, I hadn’t thought of trying to reverse the order of the arguments. You might be able to get that to work, but there is a simpler approach

moo 2018-08-29T14:46:27.000100Z

it seems too complex. Okay, I’ll keep thinking about it

moo 2018-08-29T14:53:08.000100Z

maybe I can nest functions with reduce…

moo 2018-08-29T14:54:31.000100Z

like (reduce [bunch of funcs] args)

2018-08-29T14:54:38.000100Z

That’s close, but Clojure’s version of reduce is going to let you down because it works from left-to-right

2018-08-29T14:57:02.000100Z

Let’s try something even simpler: can you write a function nester that works like this: (nester 1 2 3 4 5 6) ;; ==> [1 [2 [3 [4 [5 [6]]]]]]?

2018-08-29T14:58:25.000100Z

(nester) ;; => []
(nester 1) ;; => [1]
(nester 1 2) ;; => [1 [2]]
;; etc...

2018-08-29T15:03:21.000100Z

Here’s a version that won’t work:

(defn bad-nester [& args]
   (reduce (fn [a i] [a i]) [] args))
=> #'user/bad-nester
(bad-nester 1 2 3 4 5 6)
=> [[[[[[[] 1] 2] 3] 4] 5] 6]
This is what I meant by “clojure’s reduce will let you down because it works from left to right.”

moo 2018-08-29T15:11:38.000100Z

working on nester

moo 2018-08-29T15:11:50.000100Z

man,.. not getting this right away is bugging me. 🙂

moo 2018-08-29T15:12:35.000100Z

(defn nester 
      ([] [])
      ([x] [x])
      ([x & rest] (conj [x] (nester rest))))
(nester 1 2 3)
user=>
[1 [(2 3)]]

moo 2018-08-29T15:13:02.000100Z

I’m not sure why this isn’t working,.. because that last line should eat one element and recurse

2018-08-29T15:15:39.000100Z

You’re on the right track, but rest is a seq because of the &, so when you call (nester rest) you’re actually calling (nester (2 3)), and that’s hitting your 1-arity version.

moo 2018-08-29T15:16:40.000100Z

k so I need to convert to a vector to call the 2-arity

moo 2018-08-29T15:16:44.000100Z

lemme go try.

2018-08-29T15:17:26.000100Z

No, it’s not seq vs vector, that’ll just give you (nester [2 3])

moo 2018-08-29T15:18:02.000100Z

so… I need to explode it.

2018-08-29T15:18:25.000100Z

Whenever you’re running into (foo [x y z]) where you want (foo x y z), that’s when you need apply

moo 2018-08-29T15:18:41.000100Z

thanks!

moo 2018-08-29T15:19:04.000100Z

(defn nester 
      ([] [])
      ([x] [x])
      ([x & rest] (conj [x] (apply nester rest))))
works!

moo 2018-08-29T15:19:32.000100Z

okay, now I’m one step closer… back to it

2018-08-29T15:19:42.000100Z

also just as a stylistic/idiom suggestion, it’s common to have args that look like [x & xs] rather than [x & rest] because rest is a clojure keyword

moo 2018-08-29T15:20:40.000100Z

thanks

moo 2018-08-29T15:20:53.000100Z

xs for x-sequence?

moo 2018-08-29T15:27:48.000100Z

So this doesn’t work because the only the inner most functions handles > 0 arity…

(defn comp-nester 
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & rest] (apply f a1 rest))))
      ([f & fs] #(f (apply comp-nester fs))))

moo 2018-08-29T15:28:20.000100Z

so that means that last recursive line at the end needs to take any arity and apply…

2018-08-29T15:30:25.000100Z

“more than one x” 🙂

2018-08-29T15:30:51.000100Z

also [k & ks], [coll & colls], etc

2018-08-29T15:32:06.000100Z

The trick is that only the last arg needs to return a multi-arity function, and by the time you reach the last arg, you’re down to the single-arity version of your comp-nester

moo 2018-08-29T15:33:04.000100Z

Getting closer:

(defn comp-nester 
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & an] (apply f a1 an))))
      ([f & fs] (fn 
                   ([] (f (apply comp-nester fs)))
                   ([a1] (f (apply comp-nester fs a1)))
                   ([a1 & an] (f (apply comp-nester fs a1 an))))))

moo 2018-08-29T15:34:26.000100Z

oh

moo 2018-08-29T15:35:25.000100Z

don’t all the others needs to “pass” the context down?

moo 2018-08-29T15:35:39.000100Z

oh, … yeah they don’t

moo 2018-08-29T15:36:37.000100Z

because we’re doing (f1 (f2 (f-last many args)))

2018-08-29T15:36:43.000100Z

for your 0- and 1-arity versions, you don’t need apply btw

2018-08-29T15:36:52.000100Z

Yeah, you got it

moo 2018-08-29T15:38:27.000100Z

so, why doesn’t this work then:

(defn comp-nester 
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & an] (apply f a1 an))))
      ([f & fs] (fn [] (f (apply comp-nester fs)))))

(def neg-quot (comp-nester - /))
user=>
#'user/neg-quot
(neg-quot 6 2)
ArityException Wrong number of args (2) passed to: user/comp-nester/fn--36155  clojure.lang.AFn.throwArity (AFn.java:429)

2018-08-29T15:39:16.000100Z

Ok, that’s close

2018-08-29T15:40:12.000100Z

On the last line, (fn [] ...) needs to take 1 argument, because it’s going to be handed the result of the previous fn

moo 2018-08-29T15:40:27.000100Z

I thought so, but where do I put that arg?

moo 2018-08-29T15:40:54.000100Z

because in (f1 (f2 (f-last many args))) f1-f-last-1 are all 1-arity

2018-08-29T15:41:02.000200Z

Heh, my turn to think again 🙂

moo 2018-08-29T15:41:43.000100Z

I thought that in (f (apply comp-nester fs)), the inner most () is the 1-arity

moo 2018-08-29T15:44:40.000100Z

This also doen’t work:

(defn comp-nester 
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & an] (apply f a1 an))))
      ([f & fs] (fn [a] (f (apply comp-nester fs a)))))

2018-08-29T15:45:40.000100Z

Aha, I’ve got a clue

moo 2018-08-29T15:45:49.000100Z

(the above doesn’t make sense because in that case I’m giving args to compnester which should only take fns)

moo 2018-08-29T15:46:00.000100Z

yes!

2018-08-29T15:46:10.000100Z

(I cheated and looked at the source for comp, which is all low level stuff and hard to read, but it told me what I needed, I think)

2018-08-29T15:47:54.000100Z

So here’s the trick: if you have a list, i.e. something inside parens, Clojure assumes by default that the first item in that list is a function. In other words if you give Clojure a list that contains a function and some arguments, it’ll try and run it.

2018-08-29T15:48:40.000100Z

Oh, hrm, my nifty example isn’t working

moo 2018-08-29T15:49:12.000100Z

actually… regarding arity

moo 2018-08-29T15:50:02.000100Z

if f_k takes (x,y,z) and g=f1(f_k(x,yz), then g takes (x,y,z)

moo 2018-08-29T15:50:14.000100Z

so… I do need to pass all the args all the way down

moo 2018-08-29T15:50:32.000100Z

er,… no

moo 2018-08-29T15:50:34.000100Z

ugh

moo 2018-08-29T15:51:22.000100Z

I keep thinking in other languages… 🙂

moo 2018-08-29T15:54:12.000100Z

well, to figure this part out, I”m going back to my-2comp that only composes two functions

moo 2018-08-29T15:57:54.000100Z

this works:

moo 2018-08-29T15:57:59.000100Z

(defn my-2comp
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & an] (apply f a1 an))))
      ([f f2] (fn ([] (f (f2)))        
                ([a] (f (f2 a)))   
                ([a & as] (f (apply f2 a as))))))

moo 2018-08-29T15:59:14.000100Z

So we do need to pass the arguments down to the inner most function

moo 2018-08-29T15:59:18.000100Z

(it seems)

moo 2018-08-29T15:59:39.000100Z

so I’ll try to recurse on 3-functions

moo 2018-08-29T16:01:36.000100Z

it seems like the problem is here:

moo 2018-08-29T16:02:35.000100Z

([f f2 & fns] (fn ([] (f (f2))))        
                    ([a] (f (f2 a)))   
                    ([a & as] (f (f2 (apply my-multi-comp fns a as))))))
on the last line how do I pass the arguments so they they go to the last function and as additional arguments (functions) to my-multi-comp?

moo 2018-08-29T16:03:22.000100Z

this is why I thought I had to reverse the sequence of functions

2018-08-29T16:03:31.000100Z

Ok, it took me a minute but I think I figured it out

2018-08-29T16:03:39.000100Z

actually let me try one more test…

2018-08-29T16:06:12.000100Z

Yeah looks good

2018-08-29T16:07:41.000100Z

Now, how to explain the path from where you are to what my answer looks like :thinking_face:

moo 2018-08-29T16:08:26.000100Z

:rolling_on_the_floor_laughing:

moo 2018-08-29T16:08:39.000100Z

did my ramblings make sense above?

moo 2018-08-29T16:08:53.000100Z

regarding passing parameters down the line?

2018-08-29T16:09:36.000100Z

Yeah, and in fact I ended up passing & args thru on my anonymous functions too

2018-08-29T16:10:36.000100Z

Part of the problem is that we’re writing a function that returns a function, so when we say “args” it’s ambiguous which args we’re referring to.

2018-08-29T16:10:52.000100Z

I’m going to say “comp-args” versus “anon-args”

2018-08-29T16:11:15.000100Z

(defn my-comp [comp-args] (fn [anon-args] ... ))

moo 2018-08-29T16:11:15.000200Z

okay so we do need to pass the args, I thought so

2018-08-29T16:11:26.000100Z

Yup, you were right

2018-08-29T16:11:43.000100Z

apologies for leading you astray

moo 2018-08-29T16:12:06.000100Z

ha, don’t apologize! You’re teaching me clojure!

2018-08-29T16:12:18.000200Z

and having a blast too 😄

2018-08-29T16:13:00.000100Z

Ok, so let’s look back at defining our own comp. There’s actually 3 “simple” cases:

moo 2018-08-29T16:13:12.000100Z

k

2018-08-29T16:13:57.000100Z

(comp) ;; ==> returns identity
(comp f) ;; ==> returns f
(comp f g) ;; ==> returns (fn [& args] (f (apply g args)))

moo 2018-08-29T16:14:11.000100Z

yes

moo 2018-08-29T16:14:17.000100Z

I didn’t think of case #1

2018-08-29T16:14:42.000100Z

Yeah, that’s a clojurism: “try to make zero-arity calls return something sensible”

moo 2018-08-29T16:15:29.000100Z

does [& args] cover all arities?

2018-08-29T16:16:22.000100Z

Essentially. The combination of & args and (apply f args) works the same as if you had just called f with whatever args.

moo 2018-08-29T16:16:44.000100Z

cool so this:

moo 2018-08-29T16:16:52.000100Z

(defn my-2comp
      ([f] (fn 
            ([] f)
            ([a1] (f a1))
            ([a1 & an] (apply f a1 an))))
      ([f f2] (fn ([] (f (f2)))        
                ([a] (f (f2 a)))   
                ([a & as] (f (apply f2 a as))))))
could have been:

2018-08-29T16:16:52.000200Z

so your anonymous function doesn’t need to count anon-args

moo 2018-08-29T16:17:43.000100Z

(defn my-2comp
      ([f] (fn 
            ([& an] (apply f an))))
      ([f f2] (fn  [& as] (f (apply f2 as))))))

moo 2018-08-29T16:17:50.000100Z

(with the correct parens…)

2018-08-29T16:18:19.000100Z

Yes, exactly

moo 2018-08-29T16:19:12.000100Z

so, how to recurse and deal with that…?

moo 2018-08-29T16:21:34.000100Z

Dude, this problem gets a little † for hard.

2018-08-29T16:21:40.000100Z

Ok, so our story so far…

(defn my-comp
  ([] identity)
  ([f] f)
  ([f g]
   (fn [& args]
     (f (apply g args))))
  ([f g & more]
    ;; hmmm... ?
    ))

moo 2018-08-29T16:22:00.000100Z

yep

2018-08-29T16:22:42.000100Z

We have f, g, and more, and we want to recursively call my-comp with a shorter list of comp-args.

moo 2018-08-29T16:23:31.000100Z

yes, and jumping ahead I imagine once we’ve worked up to [f g & more] it’ll be clear how to recurse at just two functions

moo 2018-08-29T16:23:46.000100Z

but, yes, I’m with you so far

moo 2018-08-29T16:25:05.000100Z

I was here:

([f f2 & fns] (fn ([] (f (f2))))        
                    ([a] (f (f2 a)))   
                    ([a & as] (f (f2 (apply my-multi-comp fns a as))))))

moo 2018-08-29T16:25:10.000100Z

which is wrong

2018-08-29T16:25:20.000100Z

So given (my-comp f g h i j) we want to end up with a function something like this:

(fn [& args]
    (f (g (h (i (apply j args))))))

moo 2018-08-29T16:25:27.000100Z

yes

moo 2018-08-29T16:26:02.000100Z

I’m slightly frustrated that it hasn

moo 2018-08-29T16:26:29.000100Z

’t popped out at me yet. Because this sound like an if for the end case and nester

moo 2018-08-29T16:26:38.000100Z

anyways, back to listening

2018-08-29T16:26:39.000100Z

So notice that this bit (i (apply j args)) is what we get from our 2-arity version of my-comp

moo 2018-08-29T16:26:58.000100Z

yes

2018-08-29T16:28:39.000100Z

So let’s substitute:

(fn [& args]
  (f (g (h (my-comp i j)))))

2018-08-29T16:29:06.000100Z

(Hmm, am I doing that right?)

2018-08-29T16:29:46.000100Z

Whoops, no, it’s this:

(fn [& args]
  (f (g (h (apply (my-comp i j) args)))))

moo 2018-08-29T16:30:13.000100Z

yes, this is making sense

moo 2018-08-29T16:31:08.000100Z

I didn’t see where the args would go and how’d they’d get passed, but this looks right

moo 2018-08-29T16:31:24.000100Z

args go to the result of the final two-arity call

2018-08-29T16:31:25.000100Z

Ok, now we’ve got the same pattern again: (h (apply (my-comp i j) args)) matches what we get from our 2-arity version of my-comp

2018-08-29T16:32:05.000100Z

apart from the brain cramp you get from manually unrolling a recursion, lol

2018-08-29T16:32:37.000100Z

Tell you what, let’s use a let to make it a bit easier to read

2018-08-29T16:33:14.000100Z

(fn [& args]
  (let [ij (my-comp i j)]
    (f (g (h (apply ij args))))))

moo 2018-08-29T16:33:56.000100Z

oh,… so close

moo 2018-08-29T16:34:18.000100Z

antici… … … pation.

moo 2018-08-29T16:34:47.000100Z

let does make that clearer

2018-08-29T16:34:48.000100Z

I’m calling my-comp recursively, and making the list shorter

moo 2018-08-29T16:35:30.000100Z

can you go out one level of parens?

2018-08-29T16:35:36.000100Z

But I’m doing it manually

moo 2018-08-29T16:35:52.000100Z

([f g &h]

2018-08-29T16:36:47.000100Z

(make sure it’s & h not &h)

moo 2018-08-29T16:36:53.000100Z

k

2018-08-29T16:38:34.000100Z

Hmm, my brain is overheating trying to figure out how to explain the leap from manually combining i and j into the general recursive case

moo 2018-08-29T16:39:47.000100Z

I’m having trouble seeing how it unwinds

moo 2018-08-29T16:40:07.000100Z

sometimes, you can see okay we unwind to the special case, then special case is like so

moo 2018-08-29T16:40:14.000100Z

feels hard today.

2018-08-29T16:40:19.000100Z

Yeah, it’s something I’ve learned to do without thinking, which means it’s really hard to explain the thinking

moo 2018-08-29T16:40:21.000100Z

but, the special case it those last two

2018-08-29T16:41:41.000100Z

Ok, let’s look at just the h arg and following, before and after

moo 2018-08-29T16:42:15.000100Z

I feel like I should imagine this

moo 2018-08-29T16:42:55.000100Z

(f1 (unprocessed stuff)) (f1 (f2 (unproccessed stuff))) (f1 (f2 (fn args))))

moo 2018-08-29T16:43:17.000100Z

and thinking this way

moo 2018-08-29T16:43:30.000100Z

the two --> one list eating step is

moo 2018-08-29T16:43:52.000100Z

(f1_f2 (recurse here))

moo 2018-08-29T16:44:16.000100Z

that’ll work with “eating the head”

moo 2018-08-29T16:44:18.000200Z

so….

2018-08-29T16:44:46.000100Z

Ok, check this out:

(fn [& args]
  (f (g (h (i (apply j args))))))
           ^^^^^^^^^^^^^^^^^

(fn [& args]
  (let [ij (my-comp i j)]
        ^^^^^^^^^^^^^^^^
    (f (g (h (apply ij args))))))
             ^^^^^^^^^^^^^^^

moo 2018-08-29T16:45:11.000100Z

so I”m here

(defn comp-nester 
      ([f] (fn 
            ([& args] (apply f args))))
      ([f g] (fn ([& args] (f (apply g args)))))
      ([f g & h] (fn ([& args 
                        ;; so something to make the list shorter here... 
                       ;; use let to make fg
                       (fg (comp-nester h))])))) ; where do the args go?

moo 2018-08-29T16:45:18.000100Z

looking at yours…

2018-08-29T16:45:24.000100Z

We’re working our way from right-to-left, because that’s what we need to do to compose functions

2018-08-29T16:46:26.000100Z

The last 3 comp args are h, i, and j, and we make that list shorter by combining the last 2 args via the recursive call to (my-comp i j)

2018-08-29T16:47:42.000100Z

then we bring the args back in with apply, so instead of (h (i (apply j args))), we have (h (apply ij args))

2018-08-29T16:48:42.000100Z

(h (i (apply j args)))
(h (apply ij args))

2018-08-29T16:49:25.000100Z

so it’s “eating” the last 2 comp args, but keeping the general shape of (apply ___ args) at the end

moo 2018-08-29T16:50:06.000100Z

oh, was I thinking left to right?

2018-08-29T16:50:36.000100Z

probably. It’s the right-to-left bit that’s tricky about this problem

moo 2018-08-29T16:51:07.000100Z

so the args get passed because we recursively use apply

2018-08-29T16:51:26.000100Z

right

2018-08-29T16:51:30.000100Z

Each time you recurse, you want the (apply ___ args) to absorb the next arg to its left.

moo 2018-08-29T16:51:48.000100Z

okay, so I’m not 100% there

moo 2018-08-29T16:51:56.000100Z

this is why I was looking at rseq

2018-08-29T16:52:34.000100Z

Yeah, rseq will make the problem easier, but if you can figure it out without rseq you’ll have a much more powerful understanding of recursion.

moo 2018-08-29T16:52:34.000200Z

in the nester case, it was just (x (recurse the rest))

moo 2018-08-29T16:52:54.000100Z

okay, thanks so much !

moo 2018-08-29T16:53:19.000100Z

I’m out of clojure minutes for the day. I’ll puzzle it out a little more tomorrow and see what I can’t figure with all the hints

2018-08-29T16:53:35.000100Z

Sounds good, have fun and good luck

moo 2018-08-29T17:37:09.000200Z

I couldn’t help myself. I got it! But I came away with questions. my-comp1 works, and my-comp3 doesn’t and it’s not clear to me why. Am I not “eating” in version 3?

(defn my-comp1
      ([] identity)
      ([f] (fn 
            ([& args] (apply f args))))
      ([f g] (fn ([& args] (f (apply g args)))))
      ([f g & h] (fn [& args]
                     (let [h_rest (apply my-comp h)]
                          (f (g (apply h_rest args)))))))
(defn my-comp2
      ([] identity)
      ([f] (fn 
            ([& args] (apply f args))))
      ([f g] (fn ([& args] (f (apply g args)))))
      ([f g & h] (fn [& args]
                    (f (g (apply (apply my-comp h) args))))))                                    
(defn my-comp3
      ([] identity)
      ([f] (fn 
            ([& args] (apply f args))))
      ([f & g] (fn [& args]
                 (let [g_rest (apply my-comp g)]
                      (f (apply g_rest args))))))

2018-08-29T17:42:41.000100Z

First a quick comment: returning (fn [& args) (apply f args)) is the same as just returning f, so I’d just return f in that case.

2018-08-29T17:54:20.000100Z

Actually, your my-comp3 seems to be working for me, except not quite how I would expect

2018-08-29T17:54:55.000100Z

I added some diagnostic code to it and ran it, check it out:

(defn my-comp3
  ([] identity)
  ([f] (prn :what :composing-arity-1 :f f)
    f)
  ([f & g]
   (prn {:what :composing-more :f f :g g})
   (fn [& args]
     (let [g_rest (apply my-comp3 g)]
       (prn {:what :calling :args args :f f :g g :g_rest g_rest})
       (f (apply g_rest args))))))
=> #'user/my-comp3
(def double-inc-neg-quot (my-comp3 inc inc - /))
{:what :composing-more, :f #function[clojure.core/inc], :g (#function[clojure.core/inc] #function[clojure.core/-] #function[clojure.core//])}
=> #'user/double-inc-neg-quot
(double-inc-neg-quot 6 2)
{:what :composing-more, :f #function[clojure.core/inc], :g (#function[clojure.core/-] #function[clojure.core//])}
{:what :calling, :args (6 2), :f #function[clojure.core/inc], :g (#function[clojure.core/inc] #function[clojure.core/-] #function[clojure.core//]), :g_rest #function[user/my-comp3/fn--42232]}
{:what :composing-more, :f #function[clojure.core/-], :g (#function[clojure.core//])}
{:what :calling, :args (6 2), :f #function[clojure.core/inc], :g (#function[clojure.core/-] #function[clojure.core//]), :g_rest #function[user/my-comp3/fn--42232]}
:what :composing-arity-1 :f #function[clojure.core//]
{:what :calling, :args (6 2), :f #function[clojure.core/-], :g (#function[clojure.core//]), :g_rest #function[clojure.core//]}
=> -1

2018-08-29T17:55:22.000100Z

gotta go, tho, it’s meeting time

2018-08-29T20:01:49.000100Z

Oh, right, the difference between your code and mine is that your let is inside the anonymous function, but in my code the anonymous function is inside the let.

2018-08-29T20:03:29.000100Z

So basically your my-comp3, with the anon-fn inside the let, would look like this:

(defn my-comp4
  ([] identity)
  ([f] f)
  ([f & g]
    (let [g2 (apply my-comp4 g)]
      (fn [& args]
        (f (apply g2 args))))))

2018-08-29T20:05:11.000100Z

That should produce the same result as my-comp3 except that my-comp4 will do all the composing up front, one time, where my-comp3 does most of the composing at run-time (i.e. when you run the result)

moo 2018-08-29T20:19:05.000100Z

I see… Thank you!

moo 2018-08-29T20:19:44.000100Z

is there a handy way to save the slack history? or just copy and paste?

2018-08-29T20:26:20.000100Z

Have to copy-n-paste, this is the free version of slack, so the history expires/disappears eventually

moo 2018-08-29T20:38:28.000100Z

gotcha

moo 2018-08-29T20:39:47.000100Z

Man, thanks again for the help!

2018-08-29T20:42:02.000100Z

My pleasure