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

Bert Weidenflunders16:12:19

ah, cheaters πŸ™‚

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 πŸ™‚