Fork me on GitHub
#beginners
<
2019-10-24
>
ssdev01:10:00

Hi folks. I have a noob question about recursion. There's something I'm not seeing and wonder if someone can help me spot it. An example - I'm watching a tutorial where someone is re-writing the filter function. I understand the logic, but I'm struggling to see where in the function we are returning the built up collection. I see that we are using cons to construct a seq, but I don't see where we are explicitly saying "ok, that seq has been built, we're done building it, no other values to go through, so lets return it." In fact, I don't see where the actual collection is that is being built. Can someone help me see what I'm sure is obvious to the rest of you?

(defn filter* [p? ls]
  (if (empty? ls)
    ()
    (if (p? (first ls))
      (cons (first ls) (filter* p? (rest ls)))
      (recur p? (rest ls)))))

seancorfield01:10:32

@ssanders There are three "arms" to the function: the first if (when ls is empty?) returns an empty list, the second if has two arms, both of which involve a recursion on filter*.

seancorfield01:10:55

So the function "bottoms out" at an empty list.

seancorfield01:10:39

The two recursive calls either prepend the first element to filtering the rest of the list, or just return the result of filtering the rest of the list.

seancorfield01:10:57

So if you had even? as the predicate and [1 2 3] as the list, you'll get a recursive call with [2 3] (third arm), then a recursive call with [3] to which we'll prepend 2 (second arm), and finally a recursive call with [] (third arm) which will hit the bottom and return () and as we unwind the calls, we prepend 2 and finally unwind to the top and have (2) as our result.

ssdev01:10:26

Hmmm. I guess I'm not following the "unwind the calls" bit. In your explanation, you said that the function "hit the bottom" and returned () If it returned an empty list how do we get a list with 2 inside?

seancorfield01:10:03

(filter* even? [1 2 3]) => (recur even? [2 3]) => (cons 2 (filter* even? [3])) and that becomes (cons 2 (recur even? [])) => (cons 2 ())

hiredman02:10:29

The substitution model as discussed here: http://www.sicpdistilled.com/section/1.1.5/ is a useful tool for understanding how expressions evaluate. http://www.sicpdistilled.com/section/1.2.1/ has examples applying it to recursive processes

ssdev02:10:51

I think I'm starting to get it. So you're just building up calls to cons right? Then once it 'hits the bottom' case, it goes through all of those calls and gives the result?

hiredman02:10:35

The way most programming languages are implemented using a stack means calls like cons are sort of suspended until their arguments are evaluated

hiredman02:10:00

So the build up of cons you see in the substitution model maps to a build up of stack frames(which are the sort of suspended bits) in a real implementation

hiredman02:10:18

There is a way to rewrite code that makes this all very explicit, called continuation passing style, which can help as long as you already understand continuations, but understanding continuations can be tricky and a lot to bite off

ssdev02:10:21

Ok. I think I see. The thing I wasn't noticing before was that build up. So calling (filter* even? (range 5)) Is actually returning the result of (cons 0(cons 2 (cons 4 ())))

ssdev02:10:36

I should have seen that earlier. Making the mental switch from building up a mutable value to building up a function is taking me longer than I'd hoped. Thanks guys

seancorfield02:10:08

@ssanders The mutable -> immutable shift is probably the hardest part of learning Clojure (or FP in general) for folks coming from most languages.

seancorfield02:10:48

Taking away assignment and how for works in most languages (mutating the loop "index") tends to set a lot of folks completely adrift...

David Pham04:10:47

Can we justify the use CLJS NodeJS backend vs a Clojure JVM backend? I heard Once that it did not make sense to impose the single thread constraint on the backend and I agree, however it seems many company still like NodeJS.

seancorfield04:10:42

@neo2551 If you are in a Node.js shop, you'll probably get more support for using ClojureScript on it than trying to bring in Clojure on the JVM.

seancorfield04:10:16

If you're already a JVM shop, then Clojure makes much more sense on the backend than trying to introduce Node.js to run ClojureScript.

Andrea Imparato10:10:04

hello, i have a very stupid question, how can i dispatch a route with secretary inside a callback from async? This is my code:

(defn successfull-call [response]
  (:success response))

(defn login-callback [response]
  (if (successfull-call response)
    (secretary/dispatch! "/home")))

(defn call-login [data]
  (let [data-channel (go (<! (http/post login-url data)))]
    (take! data-channel login-callback)))
the call is successful but the browser is not redirected to the homepage 😕

Andrea Imparato10:10:36

do i need to use events and listeners?

Oz12:10:54

Hi, I'm trying to use a class from a java library, I've imported the class, Now the java example says:

Oz12:10:56

How do I create this object in clojure?

Alex Miller (Clojure team)12:10:08

you'll need to import the inner class actually ModbusTcpMasterConfig$Builder

Alex Miller (Clojure team)12:10:26

then (.build (ModbusTcpMasterConfig$Builder. "localhost"))

Michael J Dorian15:10:32

Is there a standard way to collect a collection by increments? Like [a b c d e f g h] to [ab cd ef gh]?

manutter5115:10:21

Check out partition and partition-all

manutter5115:10:27

Should get you most of the way there.

David Reno16:10:44

I’m using spacemacs, is there a way when editing the project.clj and adding dependencies to search for and update the dependency version or do I need to search github and find the latest release tag?

David Reno18:10:17

thanks, I’ll have a look.

tschady18:10:25

tab complete everything, finds all available versions, then hotloads new dependency

seancorfield19:10:29

For folks working on 4clojure problems, there is now a dedicated channel #4clojure

👍 12