Fork me on GitHub
#beginners
<
2017-12-05
>
Bert Weidenflunders03:12:06

hey all, I just learned about tail call recursion in clojure, and I think it's great!

Bert Weidenflunders03:12:31

Is it safe to that any time you want to use recursion in clojure you can and should use loop / recur?

noisesmith03:12:56

you don't need to use loop - functions are also recur targets

noisesmith03:12:46

and many of clojure's higher order constructs (map, reduce, filter, group-by, repeatedly, iterate, etc.) are replacements for common usages of recursion, and are preferred

Bert Weidenflunders03:12:06

interesting, thanks!

Bert Weidenflunders03:12:19

so if you are not in a loop it will recursively call the function?

noisesmith03:12:20

and if you are generating or consuming a collection in order, you probably want one of clojure's dedicated functions instead of using a recursive function directly

Bert Weidenflunders03:12:30

great! good to know 👍

Bert Weidenflunders03:12:12

so you would never call the function name directly in a function, but instead use recur? Is only the one with recur tail-call optimized?

noisesmith03:12:56

there are two big exceptions: functions that need to create a branching structure (eg. something that consumes a tree or graph) and functions that are lazy

noisesmith03:12:05

in both those cases, you can't use optimized recursion

noisesmith03:12:56

(well if you lift the continuation into the heap you can turn a branching structure into an optimized recursive one but you also make the whole thing more complex and it's probably not worth it most of the time...)

noisesmith03:12:42

and by lifting the continuation you are undoing the performance benefit of the recursion unless you are able to do it in some optimized way that doesn't replicate a lambda capture...

noisesmith03:12:13

that's not stuff for #beginners though I don't think

Bert Weidenflunders03:12:44

haha true. good points though!

New To Clojure06:12:31

So then what to do if tree is large and could cause stack overflow? tree-seq doesn't look like a valid choice in this case.

hawari08:12:44

Hi everyone, I've just recently started learning about spec, then I came across this particular example in its clojure guide:

(defn ranged-rand
  "Returns random int in range start <= rand < end"
  [start end]
  (+ start (long (rand (- end start)))))

(s/fdef ranged-rand
  :args (s/and (s/cat :start int? :end int?)
               #(< (:start %) (:end %)))
  :ret int?
  :fn (s/and #(>= (:ret %) (-> % :args :start))
             #(< (:ret %) (-> % :args :end))))
I don't quite understand how the :args get evaluated. I understand that first it uses cat to evaluate each element of the argument, by tagging the predicate with a keyword. What I don't understand is the second predicate: #(< (:start %) (:end %)), what constitutes as the input of this? Since if I define another spec like (spec/def ::lesser-than #(< (:start %) (:end %))), I can't use it against a vector: (spec/def ::lesser-than #(< (:start %) (:end %))), it results in a null pointer exception.

hawari08:12:03

I've tested it around in repl, it seems that it's not that :args evaluate things differently, I've created another spec like this:

(spec/def ::correct (spec/and (spec/cat :e even? :o odd?)
                                 #(< (:e %) (:o %))))
It is valid for a vector like [4 5], but take #(< (:e %) (:o %)) predicate as another spec, then it won't work anymore, it throws an exception, which I think is obvious since the input is a vector, not a map. My question is, does (spec/cat :e even? :o odd?) along the way transform a vector [4 5] into a map {:e 4 :o 5} and then it becomes the input of the second predicate? If so, what is the reasoning of this?

jereme11:12:46

Following up on a question and solution from @cjmurphy and @ghsgd2 Why might I favor one of these over the other?

jereme11:12:31

(for [x (range 10) y (range 10) z (range 10) 
      :let [value (str x y z)]] 
  value)
vs.
(for [x (range 10) y (range 10) z (range 10)] 
  (str x y z))

rauh11:12:32

@jereme In this example no difference, but you can add another sequence after your :let. Try it out 🙂

cjmurphy11:12:46

I would favour the 2nd. I suspect the first would more make sense if you needed the value in the [] part of the for list comprehension/macro.

jereme12:12:02

Ah, I see - thanks @rauh, @cjmurphy; very clear 🙂

Bert Weidenflunders15:12:22

hey @noisesmith, I was playing around with the tail-recursion with the factorial function, but it doesn't seem to be any better than regular javascript.

Bert Weidenflunders15:12:02

for example, this seems to break around 170, with higher values of n returning "Infinity"

Bert Weidenflunders15:12:35

(defn fac 
  ([n] (fac (dec n) n))
  ([n acc]
  (if (= n 1)
    acc
   (recur (dec n) (* acc n)))))

Bert Weidenflunders15:12:54

But this javascript function works equally well up to around 170

Bert Weidenflunders15:12:59

function fac(n) {
  if (n === 0) {
   return 1; 
  } else {   
    return n * fac (--n)
  }
}

tbaldridge15:12:35

@info at 170 you're not likely to hit a stack overflow. That'd take a depth of about 2000 or so.

dpsutton15:12:46

and the infinity is resulting because you're running out of digits in your numeric type.

dpsutton15:12:16

(fact 170) -> ArithmeticException integer overflow clojure.lang.Numbers.throwIntOverflow (Numbers.java:1501) in clj

dpsutton15:12:04

although the jvm overflows on factorial of 20

admay16:12:54

You can always cast n to a bigint to get to some bigger numbers, i.e. (fac (bigint 170)) => 7257415615307998967396728211129263114716991681296451376543577798900561843401706157852350749242617459511490991237838520776666022565442753025328900773207510902400430280058295603966612599658257104398558294257568966313439612262571094946806711205568880457193340212661452800000000000000000000000000000000000000000N

dpsutton16:12:25

and realize that this doesn't exist in js (fact 170) => 7.257415615307994e+306 which is obviously incorrect

Bert Weidenflunders16:12:29

wait, why is that incorrect?

dpsutton16:12:41

because that number has about 290 zeroes at the end

dpsutton16:12:46

it's not that "round"

dpsutton16:12:07

the actual value is 7257415615307998967396728211129263114716991681296451376543577798900561843401706157852350749242617459511490991237838520776666022565442753025328900773207510902400430280058295603966612599658257104398558294257568966313439612262571094946806711205568880457193340212661452800000000000000000000000000000000000000000N

Bert Weidenflunders16:12:20

oh i see, you mean the jvm one is incorrect

dpsutton16:12:32

the float one is from js

dpsutton16:12:35

the jvm is correct

dpsutton16:12:48

7.257415615307994e+306 != 7257415615307998967396728211129263114716991681296451376543577798900561843401706157852350749242617459511490991237838520776666022565442753025328900773207510902400430280058295603966612599658257104398558294257568966313439612262571094946806711205568880457193340212661452800000000000000000000000000000000000000000

dpsutton16:12:07

the latter is from the jvm (with the `bigint cast as admay mentioned)

Bert Weidenflunders16:12:22

when I do the float on in regular js in the chrome console it returns with e notation

Bert Weidenflunders16:12:26

"7.257415615307994e+306"

dpsutton16:12:35

yes. and that is an incorrect answer

dpsutton16:12:39

it's ballpark

dpsutton16:12:15

in the same sense that 7 + 5 is around 1e1

Bert Weidenflunders16:12:40

But isn't the jvm number isn't even returning 300 digits so isn't that off by an even larger amount?

dpsutton16:12:02

it's returning the exact number

dpsutton16:12:06

that's the "answer"

Bert Weidenflunders16:12:10

hmm, I guess it is 300 digits.

dpsutton16:12:14

yeah they're all there

admay16:12:22

Here is the result as per WolframAlpha, 7257415615307998967396728211129263114716991681296451376543577798900561843401706157852350749242617459511490991237838520776666022565442753025328900773207510902400430280058295603966612599658257104398558294257568966313439612262571094946806711205568880457193340212661452800000000000000000000000000000000000000000

admay16:12:27

Same as the JVM version

dpsutton16:12:42

there are lots of zeros because there's lots of numbers with 10 as a factor, 10, 20, 30, 2 & 5, etc

Bert Weidenflunders16:12:12

wait so was that Java or JVM Clojure?

Bert Weidenflunders16:12:40

is it possible to get the exact number with cljs?

dpsutton16:12:00

actually, i think the number is exact its just printing it that way

admay16:12:01

It is possible to get the exact number in any language as long as you know what type to cast it to. The reason why the JavaScript version was ‘incorrect’ is because it was coerced into an estimate. The Clojure version that I pasted in after casting the argument to a big int is correct.

dpsutton16:12:19

(let [num (fact 165)
     den (fact 164)]
  (/ num den))

dpsutton16:12:28

correctly returns 165.

admay16:12:40

If you want the same correct answer in Cljs, just remember to cast the argument to a bigint for precision

dpsutton16:12:28

i did not know that cljs had a bigint

Bert Weidenflunders16:12:20

yeah I thought that was a java type

Bert Weidenflunders16:12:30

But just wondering, can JVM clojure calculate fac for higher numbers, like 200?

dpsutton16:12:01

i did fact of 1400 and it returned basically instantly

Alex Miller (Clojure team)16:12:17

computers are really good at multiplying

admay16:12:45

@dpsutton try factorial of 50,000

admay16:12:54

or 100,000

dpsutton16:12:08

but not adding: (+ 0.2 0.1) 0.30000000000000004

Alex Miller (Clojure team)16:12:31

hey, that’s on you for using stupid base 10 numbers :)

dpsutton16:12:00

its out of my hands ...

Alex Miller (Clojure team)21:12:09

(10 fingers - it’s literally IN your hands!)

dpsutton16:12:27

nah not going that high. emacs doesn't like scrolling big buffers 🙂

admay16:12:50

Spoiler alert, it’s a big number

dpsutton16:12:04

spoiler alert, it's restarting emacs and repls haha

Bert Weidenflunders16:12:27

man, wolfram could do it super quick.

Bert Weidenflunders16:12:33

I wonder how they are implementing it

New To Clojure16:12:08

btw I successfully got factorial of 50000 with this function (code isn't my)

(defn factorial  ([n] #(factorial n 1))  ([n acc] (if (< n 2) acc #(factorial (dec n) (*' n acc)))))
(trampoline factorial 50000)

New To Clojure16:12:23

I wonder how does it fit into the type

dpsutton16:12:19

that's some good looking scheme code 🙂

New To Clojure16:12:38

trampoline is a great feature 🙂

Bert Weidenflunders16:12:10

trampoline tells it to use tail call recursion?

Bert Weidenflunders16:12:43

could you also use "recur" instead of the function name when it's called in the function body?

New To Clojure16:12:23

something similar yes. but I wanted to learn trampoline to be able to do recursive calls with 2 functions (using each other) later.

admay16:12:02

TLDR: trampoline is used to prevent stack overflows from mutual recursion

tbaldridge17:12:06

Yep, as with a lot of methods like this there's a tradeoff that can be made. In this case it's the complexity of a trampoline and increased memory allocations.

tbaldridge17:12:42

So the code will probably be slower, but will no longer cause a Stack Overflow.

sova-soars-the-sora22:12:20

can someone teach me how to callbacks?

kgofhedgehogs22:12:26

How to get table fields in korma?

sova-soars-the-sora22:12:21

@kgofhedgehogs are you looking at the docs? http://sqlkorma.com/docs what exactly do you need?

kgofhedgehogs22:12:22

@sova, I have two tables and one function which recieves hashmap with keys for both tables. I want to get column names from both tables and distribute received hashmap in this tables

sova-soars-the-sora22:12:55

so you need column names...

kgofhedgehogs22:12:01

@sova, I can't find what I want in docs

kgofhedgehogs22:12:25

btw, Im working with postgres

sova-soars-the-sora22:12:01

so you don't know the field names already and you want to figure them out via query? I think you have to know what fields you want to ask for

kgofhedgehogs22:12:23

@sova, I dont want to hardcode fieldnames because of project in dev and they may change... Also I think its good to have some abstraction

sova-soars-the-sora22:12:37

Very true. Does (select ) do what you want?

kgofhedgehogs22:12:40

You suggest to select one row from table and get keys from result?

kgofhedgehogs22:12:22

hm. In fact this does what I want

kgofhedgehogs22:12:08

@sova, if table is empty select will return `()

kgofhedgehogs22:12:23

So this solution works only with filled tables

sova-soars-the-sora22:12:31

Aha! there must be one row?

kgofhedgehogs22:12:03

Yes, must be. But project is in very early dev and almost all tables are empty now..

kgofhedgehogs22:12:52

So what to do in case of empty table?

sova-soars-the-sora22:12:29

Try (select* tablename)

kgofhedgehogs22:12:07

@sova, > (select* message)

{:ent {:table "message", :name "message", :pk :id, :db nil, :transforms (), :prepares (), :fields [], :rel {"account" #object[clojure.lang.Delay 0x37
3ca86b {:status :pending, :val nil}], "chat" #object[clojure.lang.Delay 0x1c39c2a8 {:status :pending, :val nil}]}}, :where [], :group [], :table "mes
sage", :db nil, :fields [:korma.core/*], :joins [], :type :select, :alias nil, :modifiers [], :from [{:table "message", :name "message", :pk :id, :db
 nil, :transforms (), :prepares (), :fields [], :rel {"account" #object[clojure.lang.Delay 0x373ca86b {:status :pending, :val nil}], "chat" #object[c
lojure.lang.Delay 0x1c39c2a8 {:status :pending, :val nil}]}}], :order [], :options nil, :aliases #{}, :results :results}

kgofhedgehogs22:12:50

Columns there

Column
------------
 id
 account_id
 chat_id
 content

sova-soars-the-sora22:12:52

well a janky way to do it is to check if empty? add a phantom element, get the field names, remove the phantom

kgofhedgehogs22:12:21

This solution looks way too bad...

sova-soars-the-sora22:12:44

I'm surprised this isn't line 1 of the docs

sova-soars-the-sora22:12:01

how do people get field names in vanilla plain ol' sql?

kgofhedgehogs22:12:32

All production dbproviders what I met, have hardcoded columns...

kgofhedgehogs22:12:58

Maybe we can make (exec-raw "SELECT * FROM ...") with some postgres tricky query?

sova-soars-the-sora22:12:07

like a postgres query that will spit that out? yes that's an idea

kgofhedgehogs22:12:48

testdb=> select column_name from information_schema.columns where
testdb-> table_name='message';
 column_name 
-------------
 id
 account_id
 chat_id
 content
(4 rows)

kgofhedgehogs22:12:57

Here it works in psql

kgofhedgehogs22:12:36

But in clojure: (kc/exec-raw "select column_name from information_schema.columns where table_name='message';")

CompilerException java.sql.BatchUpdateException: Batch entry 0 select column_name from information_schema.columns where table_name='message' was aborted
.  Call getNextException to see the cause., compiling:(project/scratch.clj:8:1) 

kgofhedgehogs22:12:11

(kc stands for korma.core)

kgofhedgehogs23:12:29

(kc/exec-raw "select column_name from information_schema.columns where table_name='message';" :results)

({:column_name "id"} {:column_name "account_id"} {:column_name "chat_id"} {:column_name "content"})

kgofhedgehogs23:12:18

@sova, thanks for participating in search of solution 🙂