Fork me on GitHub
#beginners
<
2019-12-28
>
cjmurphy02:12:58

How should I catch that a string has a dollar in it. Tried this:

(contains? #{\$} "$100")

Enyert Vinas02:12:59

Try with .contains method from java.lang.String

Enyert Vinas02:12:07

(.contains "$100" "$")

andy.fingerhut03:12:53

Or (clojure.string/includes? "$100" "$")

cjmurphy04:12:33

Yes str/includes? - I should have reached for that right away. Thanks. Now I'm intrigued. (contains? #{\u0024} "$100") should return true because 0024 is the unicode for the dollar sign - but returns false.

seancorfield04:12:14

contains? expects a collection first and then a single item -- which a string is not.

cjmurphy04:12:14

So (some #{\u0024} "$100") indeed works.

cjmurphy04:12:40

As does (some #{\$} "$100") . My real mistake was using contains? when should have been using some .

seancorfield04:12:29

@U0D5RN0S1 FWIW contains? is almost never the right choice...

seancorfield04:12:55

...except when it is (does this collection contain this specific key?).

jaihindhreddy14:12:46

contains? should be used to check whether a key exists in an associative collection, which a string is not. Maps are obviously associative, but vectors are also associative, they associate indexes with elements. This is also why you can use get with vectors and also destructure them as a map.

(let [{x 1} [:a :b]]
  x) ; Returns :b

👍 4
cjmurphy02:12:07

But returns false.

Vincent Cantin12:12:54

Hi, I don’t understand why this is not matching:

(s/explain (s/map-of symbol? string?) {:color "red"})

Vincent Cantin12:12:14

I also don’t understand the explanation.

bronsa12:12:23

:color is a keyword not a symbol

Vincent Cantin12:12:12

Thank you (I must be tired)

Alper Cugun12:12:19

I want to calculate the sum of all depths in a tree with a basic recur but it seems that I can’t recur over every branch in the tree easily? I guess I could map every element to its depth, flatten and sum but that feels like cheating. I googled but didn’t find anything exactly like this (though I did find half a dozen terse ways to walk over a tree).

Alper Cugun12:12:04

Do I have to recur on the concatenation of the branches?

👍 4
Vincent Cantin13:12:41

loop/recur are matching linear loops, not tree recursions

Vincent Cantin13:12:07

cutting your tree feels inevitable

Alper Cugun13:12:56

OK. Then it makes sense why I was beating my head against a wall.

Vincent Cantin13:12:01

Do you have to use loop/recur? like it’s a requirement for a programming challenge?

Alper Cugun13:12:58

All documentation says that normal recursion is harmful.

4
Vincent Cantin13:12:19

Alternatively, you could use: • a classic recursion pattern, • a functional zipper pattern - more info at https://vincent.404.taipei/clojure/building-trees-with-and-without-zippers/

Vincent Cantin13:12:16

Which documentation says that?

Alper Cugun13:12:05

Most stuff I read eschewed it. Don’t have any concrete pointers.

Vincent Cantin14:12:11

I guess it was a warning on the risk of stack’s overflow.

hindol14:12:35

Yeah, normal recursion against uncontrolled input is indeed harmful. Clojure blows the stack pretty fast. The same thing might run in Java just fine. But if you know the input is manageable, you can use recursion. Recur replaces the current call with the new call, so must be at the tail position. And you cannot branch, as Vincent said.

hindol14:12:41

If you must use recur, maintain a path to the root yourself in a stack in the loop bindings. Everytime you descend, you add to the stack, everytime you ascend, pop one and go there (basically what the stack does for you).

andy.fingerhut18:12:03

Often tree data structures you create will not be so deep that they will cause the max stack depth to be exceeded, and recursion is perfectly normal. Normal recursion for a Clojure sequence of 1 million elements will blow the stack limits in the JVM, but a tree with depth 100 will not, and if you are dealing with trees that deep, they are almost certainly not "balanced" ones.

didibus19:12:45

There's some misunderstandings in here. There's nothing wrong or dangerous about recursion. It works like in every other imperative language. Works the same as in ruby, python, java, c#, etc.

didibus19:12:08

When your algorithm requires a stack, using stack based recursion is the way to go

andy.fingerhut19:12:18

But you can probably understand, given the stack depth limit of default JVMs that limit call stack depths to a few thousand deep calls stacks or so, why people might write articles recommending against using recursion for processing sequences that can be longer than that.

didibus19:12:21

In Clojure, stack based recursion is done like in any other language, by calling a function recursively

andy.fingerhut19:12:01

I would not be surprised if such recommendations are not detailed enough in explaining when it is OK, vs. when it is a bad idea.

didibus19:12:42

Where the confusion probably happened is that, when trying to implement an iterative algorithm, one which does not require a stack, the use of stack based recursion is not ideal, it uses more memory and can stackoverflow. For those algorithms you want to use stack less recursion. In Clojure that's done with loop/recur or just recur inside a function.

andy.fingerhut19:12:43

Not everyone goes into depth when writing blog articles, giving nuances, reasons, etc., and not everyone reading them picks up on the nuances even if they are there ...

didibus19:12:19

Ya, that's why I said misunderstanding, not misinformation.

didibus19:12:34

Recursion is something thats long been intertwined between its concept and its classical language construct

didibus19:12:45

So when Clojure introduces more than one way to perform recursion, I can see it being confusing.

andy.fingerhut19:12:59

Well, there could be some blog article writers at fault here, too, if this is a correct impression from the OP: "All documentation says that normal recursion is harmful."

didibus19:12:27

I can imagine some blog posts from people coming from a TCO language talking about the lack of TCO in Clojure as a downside or problem and to avoid recursion because of it.

andy.fingerhut19:12:37

(I'm not saying it definitely is a correct impression, since they do not have links to what they read giving them that impression.)

didibus19:12:17

I feel I encountered some of those when I started

didibus19:12:15

Anyways, for OP, use recursion when you need a stack as part of your algorithm. So most everything where you'd use recursion in an imperative lang for, then do the same in Clojure.

didibus19:12:14

And when you don't need a stack, use recur instead, optionally with loop. This is for every time you would use a for loop or while loop in imperative langs

didibus19:12:48

Now, both fn based recursion and recur based recursion are forms of recursion. The former is stackfull, and the latter is stackless.

didibus19:12:05

Most other imperative language don't have a stackless variant of recursion. They only have sackfull recursion. Clojure has both. And the reason is that for loops and while loops need mutable variables. But Clojure is immutable, so recursion must be used in their place, except for loops and while loops are stackless, and so Clojure has a stackless recursive facility for cases where the algorithms need that.

hindol20:12:48

I am not sure why exactly, but I remember blowing up the stack pretty often while doing ProjectEuler problems in Clojure. I agree, Clojure recursion is just normal recursion but I have a feeling (again, not 100% sure) that Clojure puts more things on the stack per frame that other languages. I also say this because, I did many other problems in Java and never had to tweak JVM parameters for recursion. I would be happy to be proven wrong, but that's my impression.

didibus21:12:34

Sounds more like you had bugs to me

didibus21:12:22

Clojure uses standard Java stacks, which in turn uses native OS stack

didibus21:12:17

I can't really think of why it would have more things on the stack. In fact, I would expect it to have less, since it encourages maps and vector arguments which get put on the heap

Alper Cugun21:12:00

Oh wow, what happened here? I’m suffering from a cold so I’m doing this in between having a lie, putting the kids to bed etc. I’ll give it another go using proper function call recursion (which you call stack here?).

didibus21:12:04

I call it stackfull or stack based recursion ya. I've seen it called stack consuming recursion in some places as well

andy.fingerhut21:12:00

recur in Clojure is a restricted kind of recursion, called "tail recursion". It is not general purpose recursion, because it has extra restrictions on when it can be used. The benefit you get by using recur is that it does not consume space in the call stack. Normal function calls, including normal recursive function calls, do.

didibus21:12:00

@UJRDALZA5 It is possible that because Clojure defaults to long and double, if your were type hinting args as long and double, they'd consume twice as much space as if in java you were using ints?

andy.fingerhut21:12:56

Probably more than most people would want to read about tail recursion here: https://en.wikipedia.org/wiki/Tail_call

hindol22:12:07

@U0K064KQV Yeah, I used to type hint a lot. Now, thankfully, I miss types a lot less. Might have been some rookie mistake as I had just started with Clojure back then. The impression still remained. After today's discussion, I will definitely revisit those solutions and figure out what the problem was.

andy.fingerhut22:12:00

There are some situations in Clojure involving functions like map and concat in recursive ways that can use more stack than one might initially expect. I don't have a pointer to any articles handy at the moment, but recall seeing some about that. They involve some details about how Clojure implements lazy sequences.

andy.fingerhut22:12:10

Ah, found one article that I've seen about this before: https://stuartsierra.com/2015/04/26/clojure-donts-concat

👍 4
didibus22:12:00

@UJRDALZA5 So, I thought about it some more. And stack frames only have two variables that define their size. The number and type of local variables and the operand stack. Basically the more local variables the bigger the frame, and the more operations you perform in it, the bigger the frame as well

didibus22:12:49

It is possible on average Clojure has more local variables, especially because locals are immutable, so you often need more local variables.

didibus22:12:19

But that's all hypothetical.

didibus22:12:50

Someone would need to perform some analysis to know if Clojure on avg consumes a bit more stack space per frame than java

hindol08:12:50

I have also found this: https://clojure.org/reference/lazy#_dont_hang_onto_your_head which can explain why I was getting so many exceptions. Since ProjectEuler is very mathematical in nature, I was often creating lazy infinite sequences and then may have been accidentally retaining the sequence from the beginning.

Alper Cugun08:12:15

So yeah simple recursion turns into a bit of a mess with the call being something like (reduce + depth (map sum-depths (inc depth) rest)) which isn’t really correct and seems hard to make correct with partial or something.

Alper Cugun12:12:47

Finally solved it with a per level stack and recur (starting off with a faux stack at the tree root): https://stackoverflow.com/a/59519407/102315 Thanks for all the help, this was proper vexing but also generated a lot of learning.

didibus00:12:05

@UJRDALZA5 Eh... Ya I was still under the impression you most likely just had bug in your code :p, but I didn't want to dismiss the possibility since avg stack frame size is not something I've ever benchmarked

Markus Str17:12:10

Hello, happy to finally arrive in LISPLand 🙂

8
lisphug 4
Luis Santos18:12:36

Hi everyone. I'm trying to solve the AoC day 2 (https://adventofcode.com/2019/day/2) using clojure spec. And I'm struggling to understand how to use spec to split a sequence. Could someone help me understand what kind of construct should I use to split a list. My goal is to conform (1 2 3 4 5 6 7 8 ) into (1 2 3 4) (5 6 7 8 ). Thanks.

jaihindhreddy21:12:10

You can use take to take the first n things. Alternatively, to get the 4-sized instruction at some index idx in the vector mem, you can do (subvec mem idx (+ idx 4)), this will give you a vector of size 4

hindol21:12:52

You can use spec/cat like here: https://clojure.org/about/spec See the Sequences section.

craftybones10:12:33

Ah he wants spec

craftybones10:12:38

Interesting. DIdn’t see that 😄

Luis Santos23:12:36

Thanks @U883WCP5Z and @UJRDALZA5 I will have a look.

craftybones18:12:18

@luis559 - several ways to do it. Try split-at ?

littleli18:12:39

Can I use documentation string with deftype ?

andy.fingerhut19:12:08

Do not take this answer as authoritative, since I haven't examined the deftype source code recently to confirm, but I don't think so. Nothing in the doc string for deftype says anything about it being supported, and I don't recall seeing a deftype with one.

littleli19:12:18

No worries 🙂 It's just inconvenience and nothing serious.

dpsutton19:12:50

Most deftypes end up with a function constructor and the docstring goes there often

littleli19:12:52

hm, I would like to document constructor arguments rather then implementing methods. Methods are rather clear, and documentation is usually available already by interface method (javadoc).

dpsutton19:12:57

I don't know what you mean

dpsutton19:12:39

(deftype Foo [bar]) and (defn foo [bar] "A bar is a qux which ... " ...)

littleli19:12:40

example:

(deftype ImageSelection [data]
         Transferable
         (getTransferDataFlavors
          [this]
          (into-array DataFlavor [DataFlavor/imageFlavor]))

littleli19:12:46

I want to document what data is

dpsutton19:12:06

right. put that in the constructor function

littleli19:12:53

I don't understand what constructor function is

littleli19:12:57

I can use (new Foo :bar) or (->Foo :bar), no other functions are required

andy.fingerhut19:12:04

A separate function, defined using defn, separate from the deftype definition, that when called, creates and returns an instance of that type of object.

andy.fingerhut19:12:33

It isn't required, but if you do write one, its doc string could be used for the purposes described.

dpsutton19:12:41

from clojurescript:

(deftype Reduced [val]
  IDeref
  (-deref [o] val))

(defn reduced
  "Wraps x in a way such that a reduce will terminate with the value x"
  [x]
  (Reduced. x))

littleli19:12:52

Sounds like having a defn with body implemented as reify would be more appropriate use.

andy.fingerhut19:12:00

Using the doc string on a separate defn is a suggestion -- use it as you will, or don't 🙂

littleli19:12:56

yes, for documentation purposes it is nice. But I'm building wrappers around java.util.function.Function and I would like to avoid more indirections than necessary. That's the reason why I'm resisting this 🙂

andy.fingerhut19:12:42

You can write your own custom macro that bundles together combinations of definitions, if you think that might help.

littleli19:12:14

Got it. Unfortunately I'm not there yet "write your own macro" 😕

andy.fingerhut19:12:21

No one here is going to make you use any of these techniques. Resistance is anything but futile.

andy.fingerhut19:12:59

I have no idea if this is of interest, but you could have a separate defn with the doc string you want, and never call the function anywhere in your code.

littleli19:12:37

I need to give it some time. Thank you both for suggestions.

andy.fingerhut19:12:58

OK, last suggestion from me today: Modify the doc string on the auto-created function ->Foo . Here is an example REPL session showing one way to do so:

andy.fingerhut19:12:14

user=> (deftype Baz [bar])
user.Baz
user=> (doc ->Baz)
-------------------------
user/->Baz
([bar])
  Positional factory function for class user.Baz.
nil

andy.fingerhut19:12:18

user=> (alter-meta! #'->Baz (fn [m] (assoc m :doc "my custom docstring")))
{:arglists ([bar]), :doc "my custom docstring", :line 1, :column 1, :file "NO_SOURCE_PATH", :name ->Baz, :ns #object[clojure.lang.Namespace 0x4e6c4c55 "user"]}
user=> (doc ->Baz)
-------------------------
user/->Baz
([bar])
  my custom docstring
nil

littleli19:12:23

Wow. It's promising 🖤

seancorfield20:12:24

FWIW, we use alter-meta! quite a bit to add documentation to things that don't have that exposed -- constructor functions are one example, but also defonce, and a few other things.

littleli20:12:57

It works really great actually

didibus20:12:28

Sigh.. Ya this is another one of the few quirks I'm aware of in Clojure. Not all the def fns are consistent in their features and syntax.

didibus20:12:31

One thing that helps with doc-string is that its actually Vars that support being documented

didibus20:12:08

So anything that has no associated Var can't be documented with a doc-string

didibus20:12:40

Well, to be more complete, specs, special forms and namespaces also have doc.

didibus20:12:20

Apart from that its only Var. But not all Var creating fns or macro allow you to out the doc string on the Var they create unfortunatly

seancorfield20:12:22

For constructs that directly generate a Java interface or class, there is nowhere to "hang" a docstring.

littleli20:12:26

I'm quite a noob, but it seems that these constructing macros somehow could be improved in such a way that meta could be passed optionally.

littleli20:12:51

aw, you're right

didibus20:12:07

Yes, some of these constructing macros could be

andy.fingerhut20:12:27

I mean, someone could try to create a de facto system for hanging doc strings off of its own in-memory map, similar to the way that Clojure spec puts all of its specs into a spec-specific process-wide map, but nothing like that has been introduced, or if it has, it hasn't caught on in widespread use.

didibus20:12:55

Actually, there is one already for special forms

didibus20:12:59

The special-doc-map

didibus20:12:10

Not something you can extend yourself though

andy.fingerhut20:12:14

But if one was going to do so to work for a much larger class of 'things to be documented', you would probably want to design it to work with deftype methods, multimethods, protocol functions, Java classes, etc. and perhaps even extensible beyond those.

littleli20:12:21

Btw for my case combination of defn constructor function + reify is also an option, probably with no documentation issue at all.

didibus20:12:21

I think for some of those Sean is right. Finding a place to hang the doc isn't trivial

seancorfield20:12:51

@U0K064KQV clojure.repl/special-doc-map is private -- just an implementation detail of the doc function. I mention that because I've seen suggestions to hack that map to add docstrings for more things.

👍 4
didibus20:12:31

I think stuff like this is considered super low priority by the core team though. And I feel they also believe it is quite complex and so they prefer keeping it in the status quo state, then taking a possible jab at it that make things worse. My personal impression

andy.fingerhut20:12:28

No argument there. When I say 'de facto', I was assuming the core team would not be doing it, but some highly motivated third party that either hasn't taken up the job, or it has happened already and didn't take off.

didibus20:12:51

Fair enough

didibus20:12:35

The funny thing is, its because Clojurw gives you reified doc-strings that you also realize that not everything can have one

seancorfield20:12:45

Example of hacking the special doc map:

user=> (deftype Foo [bar])
user.Foo
user=> (doc Foo)
Syntax error compiling var at (REPL:1:1).
Expecting var, but Foo is mapped to class user.Foo
user=> (alter-var-root #'clojure.repl/special-doc-map #(assoc % 'Foo {:forms ['(Foo bar)] :doc "Foo is a magical type!"}))
...
user=> (doc Foo)
-------------------------
Foo
  (Foo bar)
Special Form
  Foo is a magical type!

  Please see 
  Foo is a magical type!
nil
user=>
The "Special Form" stuff is backed into the doc function but, hey, we can get usage docs on a type this way 🙂

didibus20:12:48

Most other languages have no form of reified doc

andy.fingerhut20:12:51

With a custom in-memory database that did not restrict itself to Vars, everything could have one.

didibus20:12:51

And use comments for doc, and then doc is read from source files

andy.fingerhut20:12:10

I use "database" loosely there.

didibus20:12:16

Ha! I thought you were advising against doing that 😛

didibus20:12:36

I think its harder. The issue is lookup

didibus20:12:46

What's the key for a class method?

didibus20:12:15

Or say you wanted doc for an atom? Not on the Var holding it? How do you reference that?

andy.fingerhut20:12:17

There could be multiple indexes, each with their own key, but one "master list" could use a key like {:kind :class-method, :class "Foo", :method "my_meth"}

andy.fingerhut20:12:01

There doesn't have to be only one key/index, and shouldn't be, if you want fast lookup using different ways of looking things up.

didibus20:12:34

Well I mean that, I can see Types have doc. And having a mapping from Type to doc

didibus20:12:44

But things that have no Class ?

didibus20:12:55

Or no unique class

andy.fingerhut20:12:57

but linear scanning through all of them would still be a reasonably fast option, which apropos does, for example, and also find-doc -- they work plenty fast for my purposes when I use them.

andy.fingerhut20:12:47

If you can be specific on "a thing" with an example, I would be very surprised if one cannot devise one or more keys to describe them.

didibus20:12:45

I guess a Class -> doc-string mapping with some special handling for certain known classes

didibus20:12:08

Well for example if I want to add doc to a lambda?

didibus20:12:33

(fn "add two nums" [a b] (+ a b))

andy.fingerhut20:12:42

Fine, then, "a thing that has a name", then 🙂

andy.fingerhut21:12:00

A thing that has a globally unique name within a JVM process, at least.

didibus21:12:20

Ya that could be done :p

seancorfield21:12:47

user=> (def x (with-meta (fn [foo] "bar") {:doc "I take an arbitrary foo and return the string bar."}))
#'user/x
user=> x
#object[clojure.lang.AFunction$1 0x2ba33e2c "clojure.lang.AFunction$1@2ba33e2c"]
user=> (meta x)
{:doc "I take an arbitrary foo and return the string bar."}
user=> (doc x)
-------------------------
user/x
nil
🙂

seancorfield21:12:40

The "docstring" there is attached to the function not the var.

andy.fingerhut21:12:26

If one did have a system that let you attach doc strings to all kinds of named things, would you see much additional value in also attaching doc strings to things without names? It seems to be significantly diminishing returns to me, personally, but perhaps I am not being imaginative enough.

didibus21:12:57

I feel that's what they're doing though? What has a globally unique name? Vars, Specs, Namespaces, SpecialForms

didibus21:12:14

What else is missing? I guess Classes

littleli21:12:03

hmm, I implemented using def ^:private to hold class (reify) and created my own constructor function and I can now document both. It's more typing, but everything is obvious now

andy.fingerhut21:12:03

Multimethods, protocols, defrecord

didibus21:12:45

What's their name?

andy.fingerhut21:12:53

well, some of them require a namespace to make them globally unique, but I am assuming that is there, and goodnesses.

andy.fingerhut21:12:31

They all have names when you define them.

seancorfield21:12:10

protocols can have docstrings (for the protocol and for the methods)

didibus21:12:12

Well, like defrecord creates a Class. The Class has a name, and it creates a constructor that also has a name. So I admit it seems what's missing is doc support for Classes

andy.fingerhut21:12:20

I think we are sort of brainstorming on what this third-party doc system might look like, if there were one. It could perhaps somehow incorporate its own copy of doc strings for things that already support them in vanilla Clojure, but I haven't thought through a design to see whether that is a good or bad idea.

seancorfield21:12:33

multimethods can have some documentation -- on the defmulti but not on defmethod

didibus21:12:14

Ya, definitly just brain storming for the fun of it rn

didibus21:12:53

What is a defmethod anyways? Under the hood? Is there some registry from multi to its methods somewhere?

andy.fingerhut21:12:11

I fully believe I could try spending a month or three designing such a thing as best I could, and implementing it, and yet it would not be used much. Just my guess, but that is why it isn't high on my list of priorities.

seancorfield21:12:27

Changing clojure.repl/doc to be more extensible could go a long way here and doesn't sound very controversial to me.

👍 4
andy.fingerhut21:12:41

You mean alter-var-root! on clojure.repl/doc to replace its definition? It doesn't have to be controversial not to get into core before 3 years pass.

andy.fingerhut21:12:37

I have 3 to 10 years of patience for dozens of patches I write to get in, eventually, but not for something I want to use 🙂

😋 4
seancorfield21:12:40

Hahaha... yeah true. I was thinking of getting something into the core system itself tho'...

didibus21:12:54

I think having a protocol for doc would be good. Right now with spec it's like a hard-coded logic. If instead there was a docable protocol and spec implemented it. Now you could extend Classes to have doc as well

seancorfield21:12:24

There's also the question of tooling -- does all the tooling out there just rely on clojure.repl/doc to print docs? I doubt it. So "fixing" doc probably wouldn't benefit some potentially large number of people...

andy.fingerhut21:12:50

Yeah, making a system outside of core means I can use it quickly, but no guarantee of any kind of wide adoption. My main reason for brainstorming at all is ... I'm on line 🙂

andy.fingerhut21:12:04

I don't intend my "3 to 10 years of patience" as any kind of criticism, by the way. Clojure core folks find all kinds of interesting things to work on and add that I wouldn't even have imagined, e.g. tools.deps, spec, REBL, Datomic, etc. I support their choice to work on whatever they want, and to ignore whatever they don't want. I only intended to mean that if I want to use something within a year, and it requires changes to core for it to work, it makes a lot more sense to consider approaches that require 0 changes to core.

💯 4
Kamuela21:12:27

Any particular channel for those working through Brave and True?

8
Alper Cugun21:12:21

I’m working through it right now too. I’m at the end near the async bit.

Kamuela22:12:35

Nice, only ch 3/4 here

Alper Cugun22:12:03

It’s pretty breezy. Let me know if you need help.

Alper Cugun22:12:20

The book does not talk really about REPL based programming, which seems to be essential.

Kamuela22:12:36

When you say REPL-based, do you generally mean REPL and source file open, playing with a function/form in the REPL until it’s correct, then pasting it back over to the source file? Some kind of flow like that?

Alper Cugun22:12:59

Yes, but with more editor support in pasting the stuff back and forth/eval-ing etc.

Kamuela23:12:18

I’m using Cursive. Do you think it’s critical to setup Emacs?

seancorfield00:12:25

Stick with Cursive if you're comfortable with IntelliJ. It's very hard to learn both a new language and a new editor at the same time.

seancorfield00:12:33

And Emacs has a steep learning curve.

seancorfield00:12:07

I used Emacs for years but switched to Atom several years ago, initially with ProtoREPL but that's no longer maintained to I switched to Chlorine a year ago and I love that.

andy.fingerhut21:12:43

It appears there is a channel called #braveandtrue that I have never joined before. You could try there, but #beginners seems to be a perfectly reasonable place, too, if that channel doesn't have enough participation.

🙏 4
Kamuela22:12:39

A nice side effect of finding the syntax so incredibly difficult is that I’m forced to break everything into smaller and smaller functions just to keep it intelligible

andy.fingerhut22:12:04

If you choose to stick with it, you will likely become much more acclimated to the syntax in a week or three of practice.

Kamuela22:12:37

If you were able to objectively compare apples to apples somehow, do you think there’s something gained with the Lisp over C-style (of some variant)? Imagine you’ve got your functions as first class citizens, immutable data structures, etc, do you still think there’s something about Clojure that’s easier to read?

hindol22:12:38

If I may. I am hardly an expert in Clojure. Been doing this as a hobby for a year, never professionally. On the other hand, I did C++ and Java for almost 10 years. 7 of them professionally. I find Clojure syntax very easy on the eyes. If you see ( and ) then the first thing in it is special. That's all you have to know.

didibus22:12:06

@U0KLE4WHZ Oh absolutely! The Lisp syntax, and especially the Clojue one with added homoiconicity for more data-structures is a huge benefit

andy.fingerhut22:12:40

Well, there are macros that, if they are that first special thing in parens, you need to know how that macro works to understand how the rest of the parameters are used, which can differ from macro to macro...

hindol22:12:00

I had more difficulty in adapting my brain to pure functions. For example, "for" returns a sequence! And to carry the result forward, you always have to plug the output of the previous function to the input of the next one!

andy.fingerhut22:12:09

But I think there are objectively fewer rules of syntax to learn with Clojure/Lisp than almost any other programming language. That doesn't mean it is initially easy to get used to, though, especially if you have become accustomed to other languages first.

andy.fingerhut22:12:20

And learning to program with immutable data, if you are accustomed to most programming languages that emphasize mutability, can also take a bit of practice to become accustomed to, much more so than the syntax I think.

didibus22:12:45

Some benefits are enabling structural editing, easy macros, straightforward and consistent syntax, obvious order of operations, terseness, and similar syntax can be used to represent other things such as same syntax for serialization format, same syntax for html, same syntax for data-strcutures, etc.

hindol22:12:58

@U0CMVHBL2 True, macros are exceptions.

didibus22:12:14

In fact, many people used to Lisps go to great extent to add the Lisp syntax to other languages. Such as Hy for Python, Fennel for lua, C-mera for C, etc.

kenj22:12:19

Having done C style languages + Python for a long time, I personally still find LISP syntax harder to parse, even after a year of poking at Clojure as a hobby. My biggest issues seem to be lack of vertical whitespace, and the binding syntax, especially in let blocks. While it’s possible to do, it seems rare that people separate code into logical blocks with comments like I see in C style languages a lot. I also find following let bindings without = signs more difficult, especially without vertical spacing in between. The only thing that helps is when people take the time to align the the let bindings as columns like so: https://github.com/bbatsov/clojure-style-guide/issues/10

kenj22:12:51

if people don’t do that, it feels like trying to read a raw CSV file, as opposed to opening it in Excel

andy.fingerhut23:12:28

Do you read Python/C/whatever code that aligns = signs like that very much? I have seen it on occasion, but certainly not the norm.

andy.fingerhut23:12:00

But I guess your point might be that the existence of an = sign is a visual separator, even if they are not lined up with each other.

kenj23:12:30

Right, I hardly read/write code that aligns = signs, but just having = as a visual separator as opposed to a single space really helps me.

kenj23:12:16

overall, I think the thing I struggle with most is that Clojure code just tends to be dense

kenj23:12:13

An example of vertical newline spacing I tend to use, which seems to defy convention in Clojure

seancorfield00:12:07

All I can say is that Clojure gets easier to read over time... I started with C (and assembler and COBOL) back in the early '80s, then C++ starting in '92, Java in '97, and in the mid-'00s Groovy and Scala before switching to Clojure. And now I've been using Clojure in production for nearly nine years.

seancorfield00:12:46

If I have a lot of bindings in a let, I do tend to align them, but that's often a sign that the function is too long and complex and should be refactored.

seancorfield00:12:35

Clojure code can be very dense. Having much smaller functions (with good names) can really help there. But you also need to really internalize some of the idioms and abstractions in order to effectively read other people's code -- and that only comes with practice.

didibus01:12:57

Well, there is a difference between one's ability to understand the code, and the visual ease that your eyes and brain has at seeing the glyphs themselves and the structure

didibus01:12:28

I do think the latter might not be the best for Lisp syntax, but that's probably due to familiarity. Honestly, after 3 years of Clojure, when I look at some Java all I see is gibberish until I try and focus my eyes one line at a time

didibus01:12:13

That said, part of the reason you can't do the vertical spacing you're talking about is due to the nature of Lisp as well. Things are nested one in another, where as in imperative langs, instructions are flat one after the other, which allow for putting space between them and make them much more visually blocky and grid like

kenj02:12:08

You can definitly do vertial spacing. It just seems like it’s not done 90+% of the time. An example from the Aleph docs:

(defn my-consume [f stream]
  (d/loop []
    (d/chain (s/take! stream ::drained)

      ;; if we got a message, run it through `f`
      (fn [msg]
        (if (identical? ::drained msg)
          ::drained
          (f msg)))

      ;; wait for the result from `f` to be realized, and
      ;; recur, unless the stream is already drained
      (fn [result]
        (when-not (identical? ::drained result)
          (d/recur))))))

kenj02:12:05

I agree with @U04V70XH6 that reading Clojure get’s much easier over time. I’m way better than I used to be… I just still don’t feel great about it. Part of the reason I bring up the spacing/alignment is I recently started learning the basics of visual design, and I realized some of the things I do to make code easier to parse for my brain, are things visual designers do when organizing a layout. > which allow for putting space between them and make them much more visually blocky and grid like It’s funny that grids are mentioned as grids are huge in visual design. There are whole books written on grids such as “Making and Breaking the Grid”

seancorfield02:12:38

@UBHTL6AF3 Agreed. And I use vertical space if I feel it helps the eye follow the code, such as separating clauses in a long cond or case, or even sometimes in an if to separate the condition from the then-form and from the else-form. But that's also a hint that maybe refactoring ought to be considered 😉

seancorfield02:12:15

There was a great talk at either this Conj or the previous one about using tables to represent data, that also touched on grids and the patterns you can easily see once data is lined up -- nice to hear from someone else talking about how visual design can help comprehension of code @UBHTL6AF3!

bartuka22:12:08

hi ppl, good holidays! I am trying to build an CLI app using graalvm and I manage to pass through some issues to use http[s] requests from inside the app

bartuka22:12:22

however, now I have this issue with the library libsunec.so

bartuka22:12:43

this great github repo explains a little bit about the problem: https://github.com/taylorwood/clojurl

bartuka22:12:09

and they say:

If you're distributing a native image, you'll need to include libsunec. If it's in the same directory as your image you don't need to set java.library.path.

bartuka22:12:26

I could not find a way to "include" this library inside my native image

bartuka22:12:39

do you guys have some idea about how to do it?

Alex Miller (Clojure team)22:12:56

prob #graalvm is the ideal place to discuss this

bartuka22:12:25

great! thanks o/