Fork me on GitHub
#clojure
<
2017-09-03
>
donyorm08:09:06

Does gen-class allow creating javadocs for methods? Does it just convert the clojure docstring, or something else?

bronsa09:09:46

javadoc is not something that's compiled to bytecode, it's just parsed from Java source

bronsa09:09:52

so no such thing in clojure

donyorm09:09:05

so is it possible for me to add javadoc to methods created by gen-class?

hmaurer11:09:09

Quick question: transducers are functions from a reducer to another reducer. Are thy completely agnostic of the type of the first argument (accumulator) of the reducing function?

donyorm12:09:15

I honestly don't really understand what transducers are. Is there a somewhat simple introduction article to them?

schmee12:09:06

@donyorm Rich Hickeys original introduction: https://www.youtube.com/watch?v=6mTbuzafcII

piegie13:09:44

do people actually frequently use them?

piegie13:09:22

(just wondering, only just learned about them today)

reborg13:09:30

@piegie not as much as they could (probably).

cddr13:09:05

Are there any well established patterns for making some feature provided by an embedded DSL available in the host language/runtime.

noisesmith14:09:55

@cddr the best bet is to have a pyramid, with information at the bottom, a smaller API above that, and a smaller DSL on top https://www.youtube.com/watch?v=LEZv-kQUSi4&amp;feature=youtu.be&amp;t=8m0s "nothing says 'screw you' like a DSL"

noisesmith14:09:08

(youtube link jumps directly to the slide)

noisesmith14:09:37

That is, if you have any capacity to do any of that. If all the host provides is a DSL with no API or information model, then I guess you're stuck with treating the DSL as a target language of a compiler (eg. what people do with SQL and JS).

hmaurer14:09:43

Are there any transducers (or should transducers) that operate on the “accumulated” value?

hmaurer14:09:14

Or are they always supposed to pass on the accumulated value to the next reducing function untouched (or return it), and leave it to the last reducing function in the chain to touch it?

hmaurer14:09:09

I started reading about transducers yesterday and from what I gather it seems they should be completely agnostic of what the accumulated value is, and thus unable to operate on it in any way, leaving it to the last reducing function in the chain to perform an operation on the accumulated value (e.g. conj, etc)

hmaurer14:09:25

Is that correct?

noisesmith15:09:23

the transducer itself can be stateful, but there's no concept of a shared accumulator between transducing functions

noisesmith15:09:14

whether you attach the transducing function to an accumulator (eg. via transduce) is not visible to the transducing function at all

noisesmith15:09:11

but, certain stateful transducers (eg. partition-all or cat) can change the shape of the input data they receive, in a way that can look like accumulation in some sense

akiroz19:09:46

What's a common/idiomatic way to handle errors you expect (e.g. user signing-up with a taken name) and ones that you don't (e.g. couldn't connect to the database). It seem somewhat wrong to throw an exception for errors you expect since they aren't really exceptions? 🤔

didibus18:09:50

Such as:

(throw (ex-info "x must not be empty" {:type :validation-exception}))

didibus18:09:50

Throw (ex-info), and have a convention on the exception map to differentiate expected exceptions, from unexpected ones.

didibus18:09:01

Then, where you can handle such exception, using ex-data you switch on type and handle each accordingly.

didibus18:09:29

If you're not 100% Clojure, that's if you're doing a mixed java project, you could also create different exception type and switch on that instead, (throw (ValidationException. "X should not be empty. "))

noisesmith19:09:01

well, Exception and Error are two different types, (both can be thrown) - catch Exception and recovering is normal, catching Error should be limited to instances where you need an extra step before bailing - it isn't meant to be something recoverable

noisesmith19:09:08

really, you should be catching the specific failure types you know how to recover from, and either ignore or rethrow the ones you can't (I really wish this advice had been followed from the start in the code base I work on...)

akiroz19:09:48

Hmm ok, but since the Clojure try/catch dispatches on type, I should derrive the Java RuntimeException just like a java program?

noisesmith19:09:12

there are other options like conditionals and returning a response that indicates an error occurred - but the vm itself ensures you need to handle Exceptions in some way, there's no way to avoid it entirely

noisesmith19:09:10

how does the clojure try/catch differ from java there? isn't it dispatched by type in java too?

akiroz19:09:19

it is, but I was wondering if it's idiomatic to create sub-types in Clojure since I've rarely had to do this

noisesmith19:09:43

oh, in clojure I'd just use ex-info and rethrow if you don't match the specific case you were looking for

noisesmith19:09:12

ex-info to create the exception with attached data, ex-data to get the data out when you catch it

akiroz19:09:29

Ahh, ok~ thanks!

noisesmith19:09:44

yeah, that looks like more of what Halloway was calling an information model and API there - you're not being forced to construct text for an existing interpreter like with SQL

cddr19:09:37

Is there something that describes what can be "eval'd"? I was surprised that this does indeed return "Foobar" (i.e. function objects seem to be self-evaluating).

(let [foo (fn [k]
            (clojure.string/capitalize k))]
  (let [foo2 (first (eval `[~foo]))]
    (foo2 "foobar")))

cddr19:09:54

Guess they're not self-evaluating, as (not (= foo foo2))

noisesmith19:09:23

functions don't have equality by behavior

noisesmith19:09:02

let bindings are not visible to eval

noisesmith19:09:09

(without pulling some weird tricks that is)

noisesmith19:09:51

also, FYI, let bindings are visible to later bindings, you don't need a second let block there

hmaurer19:09:04

@noisesmith I just tried the following:

(def foo (fn [k] (clojure.string/capitalize k)))

(= foo (eval `~foo)) ; => true

(= foo (first (eval `[~foo]))) ; => false
why is that?

noisesmith19:09:06

oh right, thanks to `~ you aren't using the binding inside the eval

noisesmith19:09:32

@hmaurer yes, symbols of defs are resolved in eval, but this is a different thing

hmaurer19:09:23

@noisesmith yeah but why are they equal in the first expression, and not the second? shouldn’t

(first (eval `[~foo]))
resolve to the same value as
(eval `~foo)
?

hmaurer19:09:21

if I

(def bar (first (eval `[~foo])))
, bar appears to have the same behavior as foo but (= foo bar) ; => false

noisesmith19:09:24

oh wow with a function, if you eval it, you get a new instance of the same class the function defines

noisesmith19:09:52

+user=> (first (eval `[~foo]))
#object[user$foo 0x5ef6ae06 "[email protected]"]
+user=> (type *1)
user$foo
+user=> foo
#object[user$foo 0x6b8d96d9 "[email protected]"]
+user=> (type *1)
user$foo

noisesmith19:09:59

the class defines the behavior when called

hmaurer19:09:07

however

(def bar (eval `~foo))
does return the exact same function (
(= foo bar) ; => true

noisesmith19:09:08

but eval makes a distinct instance of the same function

hmaurer19:09:47

boot.user=> foo
#object[boot.user$foo 0x70a377b0 "[email protected]"]
boot.user=> (eval `[~foo])
[#object[boot.user$foo 0x6d0cec74 "[email protected]"]]
boot.user=> (eval `~foo)
#object[boot.user$foo 0x70a377b0 "[email protected]"]

hmaurer20:09:05

it only makes a distinct instance if foo is inside a vector?!

noisesmith20:09:14

see, it's still user$foo as a class, but different object identities

noisesmith20:09:23

yeah - seems so, I'm not sure why though

noisesmith20:09:40

really, if your code depends on one or the other you are probably doing something wrong though...

hmaurer20:09:48

😄 I am just curious; not planning on having my code depend on this

cddr20:09:23

Yeah I don't care about object identity. Surprised functions work at all really. But then wondering why reified instances can't be eval'd

hmaurer20:09:46

maybe there is an optimization when you have quote and unquote directly following eachothers @noisesmith ?

cddr20:09:50

(let [prog {:foo (fn [k]
                   (clojure.string/capitalize k))
                  :bar (reify Object
                           (toString [this]
                              "bar"))}]
    (eval prog))
=> Boom!

cddr20:09:50

(let [prog {:foo (fn [k]
                   (clojure.string/capitalize k))
                  :bar (reify Object
                           (toString [this]
                              "bar"))}]
    (eval prog))
=> Boom!

noisesmith20:09:28

you still don't need the extra let block

noisesmith20:09:54

+user=> (let [a 0 b (inc a) c (inc b) d (inc c)] d)
3

cddr20:09:54

Right but my question is, what types of things can go into prog, and come out the other side the same and behave the same. The docs list "Strings, numbers, characters, true, false, nil and keywords" as being self-evaluating. It seems like functions are also self-evaluating. But as the example above demonstrates, not arbitrary reified instances of Object.

noisesmith20:09:00

right, I made this a thread because it wasn't addressing your primary point

noisesmith20:09:35

you can only eval things that self eval, which means they have a prn representation that is readable

noisesmith20:09:59

functions and vars are not instances of this

noisesmith20:09:47

everything that can be fed to the reader is an Object, some of them have a readable printed form

cddr20:09:01

> functions and vars are not instances of this I thought those examples demonstrated that functions were evalable.

hmaurer20:09:43

How can I access this enum from Clojure code to call a static method on clojure.lang.Compiler? https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L339

noisesmith20:09:16

ugh, OK - you're right

noisesmith20:09:13

@hmaurer

+user=> clojure.lang.Compiler$C/STATEMENT
#object[clojure.lang.Compiler$C 0x373ebf74 "STATEMENT"]

hmaurer20:09:28

oh, why the $ syntax? what does it do?

noisesmith20:09:38

it's how the jvm represents an inner class

hmaurer20:09:51

ah, thank you. So the $ is part of the name of the class?

noisesmith20:09:04

right, and you'll only see it when it's an inner class

hmaurer20:09:15

the JVM just concats the name of the outer class with the name of the inner class?

hmaurer20:09:25

with a $ in between

noisesmith20:09:28

the name of the inner calss is Compiler$C

hmaurer20:09:32

ok, thanks 🙂

noisesmith20:09:38

the outer class name just happens to be part of the inner class name

hmaurer20:09:50

(I am looking into the eval behaviour with discussed earlier with function objcts)

noisesmith20:09:52

but it's not a structural relationship, it's a naming convention

hmaurer20:09:44

I haven’t done much java before but I assume inner classes are then a sort of weak namespacing?

noisesmith20:09:08

it's a class defined inside another class, which is how an enum is implemented

noisesmith20:09:37

so there are scoping rules that enforce isolation to some degree unless you declare things public etc.

noisesmith20:09:59

but that's not because of a structural relationship between classes, it's a separate flag thing

hmaurer20:09:11

when running (eval [foo]), it follows this code-path

hmaurer20:09:24

whereas when running (eval foo), it will lead to eval’ing a ConstantExpr, which directly returns foo

hmaurer20:09:11

I haven’t managed to track it back all the way but I guess it might call this https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L4960

hmaurer21:09:28

nevermind, that still doesn’t seem to nail it..

hmaurer21:09:11

boot.user=> (.invoke (.eval (clojure.lang.Compiler/analyze clojure.lang.Compiler$C/EXPRESSION '(fn [] [foo]))))
[#object[boot.user$foo 0x70a377b0 "[email protected]"]]

noisesmith21:09:33

but you are calling eval on a list that happens to have the symbol fn at the start

noisesmith21:09:47

take out the ' to really eval a function

hmaurer21:09:19

@noisesmith yep my bad, the actual call is

(.invoke (.eval (clojure.lang.Compiler/analyze clojure.lang.Compiler$C/EXPRESSION `(fn [] [~foo]))))

noisesmith21:09:45

that does the same thing except it also namespace qualifies fn to clojure.core/fn

hmaurer21:09:25

@noisesmith it has the same behaviour as

(eval `[~foo])

noisesmith21:09:40

but it's not the same input - where does the extra fn come in?

hmaurer21:09:06

if I tracked it correctly

noisesmith21:09:22

oh, weird, OK

hmaurer21:09:58

I’m just trying to understand why

(eval `~foo)
returns the same function object, whereas
(eval `[~foo])
returns a new instance

noisesmith21:09:10

+user=> (->> (fn [] [foo])
             (clojure.lang.Compiler/analyze clojure.lang.Compiler$C/EXPRESSION)
             (.eval)
             (.invoke))
[#object[user$foo 0x6b8d96d9 "[email protected]"]]
+user=> (->> '(fn [] [foo])
             (clojure.lang.Compiler/analyze clojure.lang.Compiler$C/EXPRESSION)
             (.eval)
             (.invoke))
[#object[user$foo 0x6b8d96d9 "[email protected]"]]
+user=> foo
#object[user$foo 0x6b8d96d9 "[email protected]"]

hmaurer21:09:59

@noisesmith if you pass (fn [] [foo]) it’s different I think. It will take this code-path: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L7009

hmaurer21:09:32

boot.user=> (clojure.lang.Compiler/analyze clojure.lang.Compiler$C/EVAL (fn [] [foo]))
#object[clojure.lang.Compiler$ConstantExpr 0x65f7b4ef "[email protected]"]

hmaurer21:09:47

I’m not sure about single-quoting, but syntax-quoting with unquote (~) leads to a different result

hmaurer21:09:57

where is foo resolved from with single quoting?

hmaurer21:09:09

if it’s not namespace-qualified

noisesmith21:09:12

it's still resolved from the current namespace - ` is for the case where expansion will happen in a different namespace which won't have your bindings

noisesmith21:09:23

it fully qualifies in order to ensure you get the expected value