Fork me on GitHub
#beginners
<
2020-07-29
>
Stuart00:07:08

Can someone help me with what is going on here?

Stuart00:07:37

I run my code in the REPL, it works OK and returns what I expect. I run it in a unit test and I see different results šŸ˜ž

Stuart00:07:12

The code is very simple

(defn gcdi [a b]
  (loop [a (Math/abs a)
         b (Math/abs b)]
    (if (zero? b)
      a
      (recur b (mod a b)))))

(defn lcmu [a b]
  (/ (* (Math/abs a) (Math/abs b)) (gcdi a b)))

(defn oper-array [fct arr init]
  (reductions fct arr))
I expect
(oper-array lcmu [53 83 54 -58 -20 56 57 10] 53)
=> (53 4399  68888340 964436760 18324298440 18324298440)
And
(lcmu 18324298440 10)
=> 18324298440
SO I write the test:
(deftest a-test
  (testing "oper" (is (= [53 4399  68888340 964436760 18324298440 18324298440]
                         (oper-array lcmu [53 83 54 -58 -20 56 57 10] 53))))
  (testing "lcmu"
    (is (= 18324298440 (lcmu 18324298440 10)))))
both fail, but you can see in the REPL i get th results I expect

Stuart00:07:23

Am I being dumb here šŸ˜„

Nassin01:07:36

oper-array doesn't return a vector

seancorfield01:07:17

@kaxaw75836 = will correctly compare a vector to a list.

Nassin01:07:19

ah ok thanks, haven use clojure tests yet, just did some visual debugging šŸ˜‰

seancorfield01:07:39

That's not about tests. That's just how = works in Clojure.

Nassin01:07:59

I mean, clojure's testing framework

seancorfield01:07:05

@qmstuart I suspect your REPL state is out of sync with your code since running tests should be starting fresh each time, based on what's in the source files, where your REPL is whatever you've evaluated into it.

Stuart01:07:35

@seancorfield, I killed the REPL and restarted it. Same results.

eval-on-point01:07:39

I get (lcmu 18324298440 10) => 5722146280

seancorfield01:07:47

My results

user=> (lcmu 18324298440 10)
18324298440
user=> (oper-array lcmu [53 83 54 -58 -20 56 57 10] 53)
(53 4399  68888340 964436760 18324298440 18324298440)

seancorfield01:07:14

JVM versions? Clojure versions?

Stuart01:07:32

Clojure 1.10.1

eval-on-point01:07:39

Clojure 1.10.1, Java 11.0.8

seancorfield01:07:40

I'm on OpenJDK 14 and 1.10.2-alpha1 respectively.

Stuart01:07:49

how do i see that?

Nassin01:07:13

hmm reductions is pretty neat

seancorfield01:07:13

user=> (clojure-version)
"1.10.2-alpha1"
user=> (System/getProperty "java.version")
"14"

Stuart01:07:32

(System/getProperty "java.version")
=> "14.0.1"

3
Stuart01:07:55

@mitchell_clojure that is the result I get when run via the test

seancorfield01:07:30

OK, I'd suggest running the tests via the command-line, outside Cursive. It's a Leiningen project? So lein test in the project directory should do it.

eval-on-point01:07:33

Just looking at the functions, the lcm of 18324298440 and 10 is not 18324298440, because 10 divides it

Stuart01:07:52

ran lein-test from terminal, same failures

Stuart01:07:16

FAIL in (a-test) (core_test.clj:6)
oper
expected: [53
           4399
           237546
           6888834
           68888340
           964436760
           18324298440
           18324298440]
  actual: (53
           4399
           237546
           6888834
           68888340
           964436760
           18324298440
           5722146280)
    diff: - [nil nil nil nil nil nil nil 18324298440]
          + [nil nil nil nil nil nil nil 5722146280]

lein test :only oper.core-test/a-test

FAIL in (a-test) (core_test.clj:9)
lcmu
expected: 18324298440
  actual: 5722146280
    diff: - 18324298440
          + 5722146280

seancorfield01:07:52

So... we both get the same (wrong) answer in the REPL...?

Stuart01:07:11

the least common multiple of 18324298440 and 10 is 18324298440

Stuart01:07:17

so I think my code is logically correct

eval-on-point01:07:30

oh right, because one is the multiple of the other

Stuart01:07:10

so I think the REPL is correct

Stuart01:07:17

I just odnt understand why there is a difference

eval-on-point01:07:43

When I step through lcmu with the debugger, it evaluates (Math/abs a) to 1144429256...

Stuart01:07:30

In my REPL i get

(Math/abs 18324298440)
=> 18324298440
In a test I get the same.

Stuart01:07:48

it passes that test

eval-on-point01:07:01

Yeah, in my repl I get the same, but when stepping through the debugger that is the point where things go wrong

jsn01:07:52

an integer overflow?

seancorfield01:07:41

It's the difference between Java 11 and Java 14.

Nassin01:07:48

the tests pass on my machine

seancorfield01:07:08

So I think some how your tests are running on a different JDK version to your REPL...

šŸ‘ 3
seancorfield01:07:27

Clojure 1.10.1
user=> (System/getProperty "java.version")
"11.0.5"
...
user=> (lcmu 18324298440 10)
gcdi 18324298440 10
loop 1144429256 10
loop 10 6
loop 6 4
loop 4 2
loop 2 0
5722146280

seancorfield01:07:35

(I added a couple of prints in there)

Stuart01:07:26

I run my tests ina terminal in IntelliJ using lein test-refresh

Stuart01:07:29

or lein-test

Stuart01:07:42

Could it be an issue with my project file?

Stuart01:07:55

(defproject oper "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url ""
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url ""}
  :dependencies [[org.clojure/clojure "1.10.1"]]
  :main ^:skip-aot oper.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}
             :dev {:plugins [[com.jakemccrary/lein-test-refresh "0.24.1"]
                             [io.aviso/pretty "0.1.37"]]
                   :dependencies [[pjstadig/humane-test-output "0.10.0"]
                                  [io.aviso/pretty "0.1.37"]]
                   :injections [(require 'pjstadig.humane-test-output)
                                (pjstadig.humane-test-output/activate!)]}})
?

seancorfield01:07:13

When I run the same code above on JDK 8 I get the 5722146280 value

seancorfield01:07:25

I ran the exact same REPL session on JDK 14 and I get this

user=> (lcmu 18324298440 10)
gcdi 18324298440 10
loop 18324298440 10
loop 10 0
18324298440

seancorfield01:07:42

So Math/abs seems to behave differently.

eval-on-point01:07:01

using type annotations seems to fix it for me

eval-on-point01:07:10

(defn gcdi [a b]
  (loop [a (Math/abs ^long a)
         b (Math/abs ^long b)]
    (if (zero? b)
      a
      (recur b (mod a b)))))

(defn lcmu [a b]
  (/ (* (Math/abs ^long a) (Math/abs ^long b)) (gcdi a b)))

eval-on-point01:07:02

so I think that there is some type trickery happening here

seancorfield01:07:19

Ah, so it could be a difference in overloading between JDK versions perhaps?

jsn01:07:47

overloading of what?

Stuart01:07:53

yeah, that fixes it for me too, thank you!

jsn01:07:24

(so it was integer overflow)

seancorfield01:07:25

According to the Java docs, there was no change there. That is definitely a weird one.

Stuart01:07:41

can i make lein use the same jdk as my repl?

Stuart01:07:15

BUt thank you guys so much! It's 2:42 am here and this has been bugging all night šŸ˜„

Nassin01:07:31

looks like it's how loop/recur treat primitives types

seancorfield01:07:01

@kaxaw75836 No, it can't be that -- we're using the same version of Clojure

seancorfield01:07:35

(i.e., Clojure isn't changing between the failing and successful runs)

seancorfield01:07:59

The difference is overload resolution based on changes in the JDK it seems...

jsn01:07:20

yeah, just typehinting all args in both functions as ^long fixes it

eval-on-point01:07:01

What is the lesson here? Use numeric-tower and stay away from java methods?

Stuart01:07:35

I didn't realise Math/abs was java, since I wasn't doing (java.util. ) or (java.foo)

jsn01:07:06

for me the lesson is that you can have an integer overflow and no exceptions without requesting unchecked math

jsn01:07:39

didn't expect that

Nassin01:07:08

@seancorfield looks like a bug in clojure between different java versions? java 14 should failt too, let/loop-bound locals can be of primitive types, having the inferred, possibly primitive type of their init-form.

seancorfield01:07:54

@kaxaw75836 You're misinterpreting that.

Nassin01:07:21

Ok, was thinking with java 8 the let is interpreting the primitive type correctly, an int, with java 14 is interpreted as a long

jsn01:07:36

user=> (defn f [x] (Math/abs x))
#'user/f
user=> (f 18324298440)
1144429256
^ I think this is really impressive

seancorfield01:07:40

It looks like a change in reflection, based on a difference in the JDK.

(! 889)-> JAVA_HOME=$OPENJDK14_HOME clj
Clojure 1.10.1
user=> (System/getProperty "java.version")
"14"
user=> (set! *warn-on-reflection* true)
true
user=> (defn foo [x] (Math/abs x))
Reflection warning, NO_SOURCE_PATH:1:15 - call to static method abs on java.lang.Math can't be resolved (argument types: unknown).
#'user/foo
user=> (foo 18324298440)
18324298440
user=> ^D

Tue Jul 28 18:55:22
(sean)-(jobs:0)-(~/clojure)
(! 890)-> JAVA_HOME=$OPENJDK11_HOME clj
Clojure 1.10.1
user=> (System/getProperty "java.version")
"11.0.5"
user=> (set! *warn-on-reflection* true)
true
user=> (defn foo [x] (Math/abs x))
Reflection warning, NO_SOURCE_PATH:1:15 - call to static method abs on java.lang.Math can't be resolved (argument types: unknown).
#'user/foo
user=> (foo 18324298440)
1144429256
user=> 
In both cases, Clojure has to fall back on reflection to figure out the call (so it's relying on the underlying JDK for that) and in JDK 8 and 11 reflection tells it one thing, but in JDK 14 it tells it something different.

seancorfield01:07:11

Math/abs is overloaded on int, long, float, and double -- the return type matches the argument type.

Nassin01:07:22

@stuart java.lang is imported by default

šŸ‘ 3
Nassin02:07:37

I guess the lesson is to type hint when working with primitives

Nassin02:07:24

so the jdk knows the correct method to call

seancorfield02:07:38

Well, a "meta-lesson" is to put (set! *warn-on-reflection* true) in every file šŸ™‚ Then you can actually see where Clojure needs some help.

šŸ‘ 6
bowie 3
seancorfield02:07:19

I tried to find some indication that reflection changed across those JDK versions but it's kinda hard to search for.

seancorfield02:07:55

@qmstuart Definitely an interesting edge case to get bitten by...

jsn02:07:25

but the scary thing is that it's not a performance thing, it's a correctness thing now, sorta

seancorfield02:07:26

Yup, I think I've seen a handful of similar issues over the years, but that's probably the simplest repro case I've ever seen šŸ™‚

Nassin02:07:34

@seancorfield so it's not the job of let to infer the primitive type?

seancorfield02:07:11

The Clojure compiler will infer a primitive type in some situations.

seancorfield02:07:47

Here's an example that cropped up the other day that surprised someone:

user=> (loop [x (pos? 1)] (when-not x (recur true)))
Syntax error (IllegalArgumentException) compiling fn* at (REPL:1:1).
 recur arg for primitive local: x is not matching primitive, had: java.lang.Boolean, needed: boolean
user=> (loop [x (true? 1)] (when-not x (recur true)))
nil
user=> 

seancorfield02:07:35

pos? is an inline function that calls clojure.lang.Numbers/isPos which is declared to return boolean (primitive).

seancorfield02:07:55

true? is a Clojure function type-hinted to return Boolean (not a primitive).

seancorfield02:07:58

and yet this version does not trigger the syntax error -- instead it runs (but blows up trying to call pos? on nil after the recur)

user=> (loop [x (pos? 1)] (when-not x (recur nil)))
Syntax error (NullPointerException) compiling fn* at (REPL:1:1).
null

seancorfield02:07:57

And then you get this "weird" situation:

user=> (loop [x (pos? 1)] (when-not x (recur (= 1 1))))
nil
user=> (loop [x (pos? 1)] (when-not x (recur true)))
Syntax error (IllegalArgumentException) compiling fn* at (REPL:1:1).
 recur arg for primitive local: x is not matching primitive, had: java.lang.Boolean, needed: boolean

seancorfield02:07:35

So even tho' one equals one is true, you get the boolean primitive from = not the Boolean from true šŸ™‚

seancorfield02:07:30

user=> (def a (pos? 1))
#'user/a
user=> (loop [x a] (when-not x (recur true)))
nil
user=> (let [a (pos? 1)] (loop [x a] (when-not x (recur true))))
Syntax error (IllegalArgumentException) compiling fn* at (REPL:1:1).
 recur arg for primitive local: x is not matching primitive, had: java.lang.Boolean, needed: boolean
In the second case, a is inferred to be primitive, which allows x to also be inferred to be primitive. In the first case, even tho' a has the exact same value as the local a, it gets promoted to a non-primitive when used for the local binding in the loop.

Nassin02:07:40

the issue there is recur then? loop/recur tries to work with primitive if posible

Nassin02:07:20

interesting

seancorfield02:07:17

loop/`let` try to infer primitive types for efficiency, but you have to be very careful to avoid promotion (to Boolean, Long, Double -- boxing), so it can be a surprising area.

Nassin02:07:44

the combo of pos? and loop/`recur` made his head spin šŸ˜‰

seancorfield02:07:53

Clojure does its best to help you avoid losing that efficiency accidentally -- especially if you always set the reflection warning flag -- but sometimes it can be harder to get the primitive type than you might expect

user=> (def a (pos? 1))
#'user/a
user=> (loop [x ^boolean a] (when-not x (recur true)))
nil
Since there's no syntax error here, you can deduce that x was not deduced to be primitive even with the attempt at type hinting...

Nassin02:07:09

`user> (type (= 1 1)) java.lang.Boolean`

seancorfield02:07:16

user=> (loop [x (boolean a)] (when-not x (recur true)))
Syntax error (IllegalArgumentException) compiling fn* at (REPL:1:1).
 recur arg for primitive local: x is not matching primitive, had: java.lang.Boolean, needed: boolean
But that works as "expected".

seancorfield02:07:20

(`source` shows us that boolean is an inline function calling clojure.lang.RT/booleanCast which has a primitive return type, if look in Clojure's Java source code)

seancorfield02:07:14

Also, be careful when trying to add "primitive type hints" because they don't always do what you expect:

user=> (run! println [(meta ^long []) (meta ^int []) (meta ^boolean [])])
{:tag #object[clojure.core$long 0x1280851e clojure.core$long@1280851e]}
{:tag #object[clojure.core$int 0x5e840abf clojure.core$int@5e840abf]}
{:tag #object[clojure.core$boolean 0x35f8a9d3 clojure.core$boolean@35f8a9d3]}
nil

seancorfield02:07:34

In this case long, int, and boolean are resolved to the Clojure core functions of those names...

stopa02:07:37

hey team, a friend asked me, about advice getting into clojure. Wanted to ask the community: do you have any project-based resources you'd recommend? Ideally it would be something, where each unit you build some cool project ā€¢ beginning, you wouldn't need any deps, and project would be simple. only use is to get the repl up + dev env ā€¢ then it gets more and more fun : ]

ozzloy03:07:27

what's the pros and cons of src/first_part/second_part/core.cljs versus src/first_part/second_part.cljs ?

seancorfield03:07:56

@ozzloy_clojurians_net Those would have namespaces first-part.second-part.core and first-part.second-part -- does "core" make sense in that context, for your project?

seancorfield03:07:10

Pick meaningful names for namespaces (and files).

ozzloy03:07:11

i'm not sure whether it's meaningful in this context. i'm creating a bingo web app to play with my family on sundays and decided to do it in clojure. this is my first clojure project, so i'm not sure what's idiomatic

seancorfield03:07:42

The only reason core.cljs seems so common is that Leiningen, long ago, decided to add .core to the ns of any project created with just a single segment -- because there are some compatibility concerns with Java (about not having a top-level class without a package name).

ozzloy03:07:49

so should i do src/ozzloy/bingo.cljs or src/ozzloy/bingo/core.cljs

seancorfield03:07:59

I would favor the former.

ozzloy03:07:13

yeah, ok, that's what i was thinking based on what you just said

seancorfield03:07:17

Unless "core" is actually a meaningful concept in your program, it's a poor name.

ozzloy03:07:53

i'll start with ozzloy/bingo.cljs and be willing to change it if necessary

seancorfield03:07:21

When you work with the Clojure CLI / deps.edn (instead of Leiningen) and you use clj-new (my descendant of lein-new), it prohibits single-segment names so you won't get .core appended šŸ™‚

ozzloy03:07:47

oh, and what about src/clj/whatever + src/cljs/whatever versus src/whatever/*.{clj,cljs,cljc} ?

ozzloy03:07:24

ah, i see. i thought i recognized your name. yeah, i'm using cli tools

seancorfield03:07:49

It's simpler to just use src/whatever/foo.clj{s,c}

ozzloy03:07:17

cool. that's what i was leaning towards too. do you know why the extra folder appears in some projects?

seancorfield03:07:18

the whole src/{language}/whatever/*.* thing is more of a Java/Maven practice

ozzloy03:07:05

super! that's nice to know. seems like clojure idioms are more like what i would do.

seancorfield03:07:31

In the Clojure Contrib libs, you'll see src/main/whatever/*.clj and src/test/whatever/*.clj partly because they are all built with Maven.

seancorfield03:07:54

In Clojure itself, you'll see src/clj/* and src/jvm/* for the Clojure source and the Java source.

ozzloy03:07:55

oh yeah, i forgot about the testing thing. some projects do /src/bla + /test/bla, some do /src/bla and src/test/bla

ozzloy03:07:33

i've also seen /test/bla_test. not sure what's up with the final "_test". do you know what that's about?

ozzloy03:07:04

also, thanks so much for answering these questions! i have no idea how i'd find out other than talking to someone who's been in the community a while

seancorfield03:07:44

My preference is src/whatever/foo.clj and test/whatever/foo_test.clj (with the namespace whatever.foo-test).

seancorfield03:07:00

The -test convention is common to a lot of tools.

ozzloy03:07:14

oooh, right the separate namespace

seancorfield03:07:25

Cognitect's test runnner assumes it (but it can be overridden).

seancorfield03:07:29

There's a cljs test runner as well (from olical I think).

ozzloy03:07:53

ok, you've convinced me.

seancorfield03:07:37

And this runs all the tests -- both as ClojureScript and as Clojure (and in multiple Clojure versions) https://github.com/seancorfield/honeysql/blob/develop/run-tests.sh

ozzloy03:07:28

is honeysql:sql::hiccup:html ?

seancorfield04:07:03

Insofar as it provides a DSL (as data structures or via helper functions) that then generates SQL, yes it's a bit like Hiccup.

ozzloy04:07:53

i could see it possibly being meaningful for the Clojure source itself to have different folders for java and clojure source code. seems like it wouldn't really be meaningful in most projects though

ozzloy04:07:41

i'll probably use honeysql in my project. thanks for these pointers. good to see some examples of testing too.

seancorfield04:07:46

And next.jdbc šŸ™‚

ozzloy03:07:48

also, how do i tell slackbot to stop "help"ing?

ksd03:07:02

Hello. I would like do something like (map quote forms) in the context of writing a Clojure to Go compiler for a school project. However:

(map quote '(a b c))
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: quote in this context
class clojure.lang.Compiler$CompilerException

ozzloy03:07:30

quote is a special form

ozzloy03:07:59

i'm not exactly sure the specifics in clojure, but map usually takes a function, not a special form

ozzloy03:07:20

oh bummer, i was hoping that would be helpful. do you have code up somewhere?

seancorfield03:07:09

@ruyvalle What is the actual input that you're starting with, and what are you trying to produce?

dpsutton03:07:39

Mapping quote doesnā€™t seem particularly helpful as the forms will be evaluated for map. Quote would get the already evaluated forms

Alex Miller (Clojure team)03:07:16

quote means "read but don't evaluate". if you already have forms, they've already been read or whatever

ksd04:07:35

@ozzloy_clojurians_net that was helpful actually šŸ™‚ @seancorfield For what Iā€™m doing right now my input is a Clojure string that has gone through read, and my output is a Clojure form (in the form of a Cons IIUC), which later gets transformed into Go code (a string). I am currently trying to implement a primitive form of macros and am almost there I think. I was experimenting with using eval and implementing if as a macro in terms of cond, but both branches were being evaluated during macroexpansion, hence why I wanted to quote stuff. I ended up applying a substitution to each term in my form, which seems to work, except that syntax-quote is read rather oddly when the quoted form is, e.g., a function application. For example I end up with

(clojure.core/seq 
  (clojure.core/concat 
    (clojure.core/list 
      (quote clojure.core/cond))
    (clojure.core/list false) 
    (clojure.core/list (/ 1 0)) 
    (clojure.core/list :else) 
    (clojure.core/list 2)))
as the expansion of (my-if false (/ 1 0) 2) . Instead what I want is (clojure.core/cond false (/1 0) :else 2).

ksd04:07:44

btw thank you everyone šŸ™‚

ksd04:07:59

also sorry if this is a little advanced for this channel šŸ˜… I started using Clojure only recently so I very much consider myself a beginner

fabrao08:07:48

hello all, how do I cast this?

(proxy [ExpectedCondition] []
                   (apply [^org.openqa.selenium.JavascriptExecutor d]
                     (clojure.pprint/print-table (:members (r/reflect d)))
                     (->
                      (.executeScript d "return document.readyState")
                      .toString
                      (.equals "complete"))))
it seems that the d is not casting to JavascriptExecutor, why?

mbarillier12:07:35

google is failing me, and apparently I can't read the clojure docs. what's the idiomatic way to apply a sequence of fns to a value, e.g. (??? [f g h] x) -> (h (g (f x)))?

yuhan12:07:23

((apply comp [f g h]) x) ?

yuhan12:07:18

ah it's reversed in your case, so (apply comp (reverse [f g h]))

mbarillier12:07:46

yeah, that'll work. seems like there should be a core library macro to do such a thing, but that'll get the job done. thanks --

yuhan12:07:55

could also use (reduce (fn [acc f] (f acc)) x [f g h])

mbarillier12:07:31

I prefer v1. šŸ™‚

Manmohan Nayal12:07:16

(-> x f g h) thread first macro can do the same

mbarillier12:07:44

unfortunately my fns are in a collection, not a known set of fns. that being said, writing a macro to expand into a threading macro would work also.

Manmohan Nayal13:07:27

only if you need the apply-comp combination too frequently, otherwise its an overkill

Ben Sless18:07:41

And reduce performs better

Enrico Teterra15:07:24

how come clojurescript doesnā€™t have agents? that feels like a big pain for doing interop

andy.fingerhut15:07:17

Perhaps because JavaScript runtimes are single-threaded? Or at least are only multithreaded via webworkers, which are not shared memory threads the way JVM threads are.

Enrico Teterra15:07:09

ah right that makes sense, thanks

Timur Latypoff07:07:46

@U0CMVHBL2 I think it would be pretty easy to ā€œemulateā€ agents even I single-threaded environment just to be compatible with clojure.core API surface. Am I missing some technical caveat in possible implementation there, or the idea was more like ā€œitā€™s probably a mistake to use agents in CLJS so we explicitly say you shouldnā€™t use themā€?

andy.fingerhut16:07:16

@UTXR46DS6 If you mean implement functions like send and send-off by immediately executing the caller-supplied update functions f before returning from send / send-off, then sure, that is pretty easy to do.

andy.fingerhut16:07:36

One could also implement ref and dosync in a single-threaded runtime quite easily.

andy.fingerhut16:07:12

Also, if you look at this ClojureScript documentation page, it mentions "Agents are currently not implemented" in the Concurrent Programming section, so it appears that at least whoever wrote that may have been thinking they might be in the future: https://clojurescript.org/about/differences

Eric Ihli16:07:12

I'm using this BigDecimal library (https://github.com/iriscouch/bigdecimal.js#readme) and I get an infinite recursion error when I try to log one to the console. How can I override the behavior of those objects being printed so that it doesn't infinitely recurse? Come to think of it... this might be something with shadow-cljs and/or binaryage/devtools.

Eric Ihli16:07:23

cljs$core$array_QMARK_ js/main/cljs-runtime/cljs.core.js:209
    cljs$core$seq js/main/cljs-runtime/cljs.core.js:4747
    2 js/main/cljs-runtime/cljs.core.js:17686
    sval js/main/cljs-runtime/cljs.core.js:11590
    cljs$core$ISeqable$_seq$arity$1 js/main/cljs-runtime/cljs.core.js:11741
    cljs$core$seq js/main/cljs-runtime/cljs.core.js:4745
    cljs$core$print_prefix_map js/main/cljs-runtime/cljs.core.js:33641
    cljs$core$print_map js/main/cljs-runtime/cljs.core.js:33650
    cljs$core$pr_writer_impl js/main/cljs-runtime/cljs.core.js:33015
    cljs$core$pr_writer js/main/cljs-runtime/cljs.core.js:33120
    print_prefix_map js/main/cljs-runtime/cljs.core.js:33640
    cljs$core$pr_sequential_writer js/main/cljs-runtime/cljs.core.js:32809
    cljs$core$print_prefix_map js/main/cljs-runtime/cljs.core.js:33629
    cljs$core$print_map js/main/cljs-runtime/cljs.core.js:33650
    cljs$core$pr_writer_impl js/main/cljs-runtime/cljs.core.js:33015
    cljs$core$pr_writer js/main/cljs-runtime/cljs.core.js:33120
    print_prefix_map js/main/cljs-runtime/cljs.core.js:33640
    cljs$core$pr_sequential_writer js/main/cljs-runtime/cljs.core.js:32822
    cljs$core$print_prefix_map js/main/cljs-runtime/cljs.core.js:33629
    cljs$core$print_map js/main/cljs-runtime/cljs.core.js:33650
    cljs$core$pr_writer_impl js/main/cljs-runtime/cljs.core.js:33015
    cljs$core$pr_writer js/main/cljs-runtime/cljs.core.js:33120
    print_prefix_map js/main/cljs-runtime/cljs.core.js:33640
    cljs$core$pr_sequential_writer js/main/cljs-runtime/cljs.core.js:32809
    cljs$core$print_prefix_map js/main/cljs-runtime/cljs.core.js:33629
    cljs$core$print_map js/main/cljs-runtime/cljs.core.js:33650
    cljs$core$pr_writer_impl js/main/cljs-runtime/cljs.core.js:33015
    cljs$core$pr_writer js/main/cljs-runtime/cljs.core.js:33120
    print_prefix_map js/main/cljs-runtime/cljs.core.js:33640

Eric Ihli16:07:43

I turned off shadow's console support by adding {:console-support false} to the build, based on https://shadow-cljs.github.io/docs/UsersGuide.html#_preloads No joy.

Eric Ihli18:07:19

From Tony Kay and Fulcro RAD:

#?(:cljs
   (extend-protocol IPrintWithWriter
     ty/TaggedValue (-pr-writer [d writer opts]
                      (let [t    (.-tag d)
                            v    (.-rep d)
                            type (case t
                                   "f" "bigdec"
                                   "n" "bigint"
                                   "tagged")]
                        (-write writer (str "#" type " \"" v "\""))))))

dhd17:07:09

hello, i have a bit of a noobish question. Where is the definition of the if special form. If i wanted to write my own if without the use of already provided conditionals (`when`, cond, etc), how can i do so?

seancorfield17:07:44

Special forms are built into the Clojure compiler so you'd have to go read the Java source code but the short answer is you can use a macro to write your own if form.

dhd17:07:20

ah I see. that's why i was not able to find it in the clojure.core lib.

seancorfield17:07:34

You need a macro in this case, not a function, because you do not want the 2nd or 3rd expression evaluated (function calls evaluate their arguments, macros do not).

dhd17:07:24

understood. i have been reading common-lisp (intro to symbolic computation) and i realize i need a macro. So to write a if macro, i would still need to use built in conditionals. I see no other way of writing it.

seancorfield17:07:35

(although I'm not sure how you'll write if without if since you need a conditional to test the truth of the test expression)

jsn17:07:07

You can make a map of falsey things to first and then (that-map cond-expr second)

seancorfield17:07:51

Given that only false and nil are falsey, yup, that's a good suggestion!

jsn17:07:59

true? could be used to further narrow it all down, too

seancorfield17:07:26

That only tests that a value is actually true -- not just truthy.

dhd17:07:58

yeah, that's the catch-22 i am stuck on. šŸ™‚

seancorfield17:07:06

Some things are so fundamental, they kind of have to be baked into the core language itself...

seancorfield17:07:41

You can write your own versions of when, cond, etc but they'll all depend on the if special form...

dhd17:07:05

agreed. thank you very much for clarifying where the if special form is defined. with the built in if i can write other conditionals using macors.

dhd17:07:09

thank you!

jsn17:07:07

You can make a map of falsey things to first and then (that-map cond-expr second)

jsn17:07:16

It would be somewhat like fake Church booleans

jsn17:07:42

Something like (((get {nil second false second} cond-expr first)[then-clause else-clause])

jsn17:07:19

Actually, this works, I think:

(defmacro if' [cond-expr then-clause else-clause]
  `(let [then-f# (fn [] ~then-clause)
         else-f# (fn [] ~else-clause)
         f# (get {nil else-f# false else-f#} ~cond-expr then-f#)]
     (f#)))

awesome 3
šŸ‘ 3