Fork me on GitHub
#beginners
<
2019-06-10
>
hp_pedrors01:06:05

Hey so another quick question: for error handling, e.g sending wrong request parameters or invalid operations, is it ok to return nil all the way up until the service can treat it and respond the request with 4xx? Meaning the request handlers will look something like:

(defn get-something
  [params]
  (let [value (get-function params)]
    (if value
      (ok-response)
      (not-ok-response))))
and then get-function will probably be something like
(defn get-function
  [params]
  (let [value (try-to-get params)]
    (if value
      (value))))
and that will probably continue... or is there a better way to do so?

seancorfield01:06:34

I think this is reasonable, unless you want to give more specific error for different failures.

seancorfield01:06:30

That second function could be

(defn get-function
  [params]
  (when-let [value (try-to-get params)]
    (value)))

seancorfield01:06:33

If you want some very simple, basic error handling/reporting, you might look at https://github.com/cognitect-labs/anomalies @hp_pedrors

hp_pedrors02:06:55

Hmm I was thinking of returning an error code message, along with the value, so I can use globals and determine which message to send as well.

seancorfield02:06:53

Anomalies supports error code (category) + message.

seancorfield02:06:02

You could return {:ok value} or {:cognitect.anomalies/category ... }

hp_pedrors02:06:05

Interesting! I'll take a look into it.

seancorfield02:06:32

It's just data. You just decide on your conventions, write a spec, and off you go.

nate_clojurians01:06:46

I have a vector that will be either 1 or 2 elements. is there a pattern-match-y way to do different behavior based on the length?

nate_clojurians01:06:21

I suppose I could have a multi-arity function and apply them to it

nate_clojurians01:06:46

it's a bit hacky though

lilactown01:06:01

Not really pattern-matching, but Iโ€™ll sometimes do:

(let [[a b] my-vec]
  (if (nil? b)
    (do-thing a)
    (do-other-thing a b)))

nate_clojurians01:06:31

ok, that's basically what I'm doing but then the variable names kind of suck

lilactown01:06:02

in what way?

nate_clojurians01:06:09

because if you named them anything meaningful then in either the single-value case or the double value case the variable names are wrong

nate_clojurians01:06:23

unless you name them something long and not-so-great

didibus01:06:03

@nate_clojurians Just use case

didibus01:06:06

cljs.user=> (case (count [1 2 3])
       #_=>   1 "one"
       #_=>   2 "two"
       #_=>   3 "three")

didibus02:06:22

You can use apply to call you various arity functions

nate_clojurians02:06:00

(defn- split->cnt+char
  ([char]
   [1 char])
  ([cnt char]
   [(parse-int cnt) char]))

(defn- encoded-string->cnt+chars
  [encoded-string]
  (->> encoded-string
       (re-seq #"\d?\D")
       (map #(apply split->cnt+char (cstr/split %1 #"")))))

nate_clojurians02:06:19

trying to run-length-decode here

nate_clojurians02:06:34

so if a character isn't preceded by a number it should default to 1

nate_clojurians02:06:45

> (encoded-string->cnt+chars "2B3CABR3T")
;; => ([2 "B"] [3 "C"] [1 "A"] [1 "B"] [1 "R"] [3 "T"])

didibus02:06:01

(case (count my-vec)
  1 (apply some-fn my-vec)
  2 (apply some-fn my-vec))

didibus02:06:05

Well if some-fn is overloading args, you don't even need case it'll be smart enough to dispatch on the argument count (edit, meant case, you still need apply)

nate_clojurians02:06:22

I guess I'm just trying too hard to make clojure work like elixir's pattern matching

didibus02:06:44

Destructuring is not pattern matching

nate_clojurians02:06:14

yes, but I was trying to make it work like it was

didibus02:06:39

I think Rich Hickey has an issue with pattern matching

nate_clojurians02:06:41

I wanted to match on the number of arguments and do different things based on it

didibus02:06:50

Because it complects two things

nate_clojurians02:06:53

which I did using the multi-arity

didibus02:06:37

So destructuring is for getting things out of a structure.

didibus02:06:53

And after that, to perform conditional logic, you use conditional constructs

nate_clojurians02:06:40

that's a strange argument

nate_clojurians02:06:46

I don't get his meaning

seancorfield02:06:47

^ @hp_pedrors That would be good for you to watch, based on our thread about error results.

nate_clojurians02:06:16

maybe not strange, just that I don't get it

didibus02:06:24

You're coupled your flow to the structure

didibus02:06:36

If you change your structure, you break all your conditional logic

nate_clojurians02:06:47

I guess you don't need to know what exactly you have if you do (get structure key-or-index-or-whatever) but you do when you pattern match?

nate_clojurians02:06:32

if I do (get-in m [:profile :address :zip-code] "no zip code!") I'm pretty sure I'm coupling to the structure here

nate_clojurians02:06:57

and it seems a bit like an implementation detail, like your pattern matching could be built around accesses like that instead of perfectly matching structures

nate_clojurians02:06:59

but what do I know

didibus02:06:01

Ya, but not your conditional logic

didibus02:06:25

Getting something out of a structure has to be coupled to the structure.

nate_clojurians02:06:45

right, which I have to do anyways in order to do conditional logic

didibus02:06:50

That is, unless it is a search, but like getting a thing in a precise position will always be coupled

nate_clojurians02:06:35

sure, which is where I'm getting stuck on the difference. access is bound to structure already, especially when you're working with a very limited number of basic structures

nate_clojurians02:06:51

and conditional logic that accesses will be bound to that structure

nate_clojurians02:06:05

whether it's pattern matching or a get-in

didibus02:06:05

What do you mean by conditional logic that accesses?

nate_clojurians02:06:41

pattern matching is conditional logic that accesses values and uses that to determine the code path to take

nate_clojurians02:06:23

whether I do that with a case or an if or pattern matching, I'm still accessing data to do the conditional and so my conditional logic is bound to the structure

nate_clojurians02:06:40

doing away with pattern matching doesn't make it uncoupled

nate_clojurians02:06:00

(get-in m [:profile :address :zip-code] "no zip code!") itself is conditional code that depends on a map with that kind of structure

nate_clojurians02:06:30

find a value within nested maps this deep or else fall back to this other value

didibus02:06:32

What do you mean? The condition would come after after, like (if "no zip code!" x y)

didibus02:06:35

Also, I'm not sure about get-in, since you're right, it does a simple for of conditional with the default argument

didibus02:06:46

But destructuring would not

didibus02:06:54

It would just return nil or the value

didibus02:06:01

Well, okay, it supports default as well ๐Ÿ˜›

nate_clojurians02:06:58

I just don't get how pattern matching couples your logic to your data structure any more than normal accessing does

nate_clojurians02:06:07

I'm not expressing myself well

nate_clojurians02:06:04

sure, positional matching sucks because it's pretty tightly coupled but whether I do matching on [x y] or (if (= 2 (count arg)) I'm still coupled to the structure

didibus02:06:10

Well, the first thing is that its a feature that complects binding and flow control. And Clojure likes to decomplect. So it chose to separate these two concerns.

nate_clojurians02:06:02

I guess they can be two separate operations and that's the benefit?

nate_clojurians02:06:15

I'm not sure I see the value, but maybe I will later

didibus02:06:24

The second thing is that pattern matching as a flow control mechanism is normally closed for extension. So Clojure offers multi-methods instead to avoid that.

nate_clojurians02:06:42

I don't know what that means, sorry

nate_clojurians02:06:48

"normal closed for extension"

didibus02:06:59

normally closed*

nate_clojurians02:06:04

still not sure what you mean

didibus02:06:11

Like, you can only extend it by modifying the pattern matching code itself

didibus02:06:05

You can't from outside the function, for example say, oh and by the way, if the data matches to X do this other thing.

didibus02:06:36

I mean, it is a pedantic argument

nate_clojurians02:06:12

you'd need to define a multimethod and start using it at the place you wanted anyways if you wanted to extend it in the first place

nate_clojurians02:06:44

it seems more like an argument that multimethods are good for extension not that pattern matching is bad in general

didibus02:06:09

Well, in practice, nothing is always bad

didibus02:06:38

But as a language feature, you have to think, I'm setting a norm, and is that norm likely to lead people to do things that I don't think is ideal most of the time?

didibus02:06:20

Pattern matching has many libraries for it in Clojure. But Rich Hickey chose to make it more cumbersome to reach for, by not putting it in the core

nate_clojurians02:06:36

it was in core at one point, looks like

nate_clojurians02:06:16

but I get the point

nate_clojurians02:06:21

I just disagree with it

didibus02:06:25

To be honest, I'm not too sure myself, and now it has been a while since I've used pattern matching. Can you remind me in Elixir how you'd solve your above problem ?

seancorfield02:06:11

> it was in core at one point, looks like Not sure what you mean there @nate_clojurians?

seancorfield02:06:43

That's a Contrib library. Not part of Clojure.

nate_clojurians02:06:45

if not core, it was a core library?

nate_clojurians02:06:05

@didibus you'd solve it much like the multi-arity thing I pasted above

nate_clojurians02:06:19

iex(1)> my_vec1 = ["A"]
["A"]
iex(2)> String.to_integer("3")
3
iex(3)> my_vec2 = ["3", "B"]
["3", "B"]
iex(4)> defmodule Foo do
...(4)>   def example(my_vec) do
...(4)>     case my_vec do
...(4)>       [char] -> [1, char]
...(4)>       [cnt, char] -> [String.to_integer(cnt), char]
...(4)>     end
...(4)>   end
...(4)> end
iex(5)> Foo.example(my_vec1)
[1, "A"]
iex(6)> Foo.example(my_vec2)
[3, "B"]

nate_clojurians02:06:52

you could also do pattern matching in the function definition, but I just left it as the case statement

didibus02:06:55

Okay, that's what I remembered

nate_clojurians02:06:02

iex(7)> defmodule Foo do
...(7)>   def example([char]) do
...(7)>     [1, char]
...(7)>   end
...(7)>
...(7)>   def example([cnt, char]) do
...(7)>     [String.to_integer(cnt), char]
...(7)>   end
...(7)> end
iex(8)> Foo.example(my_vec1)
[1, "A"]
iex(9)> Foo.example(my_vec2)
[3, "B"]

didibus02:06:14

Ya, I can only attribute it to wanting to encourage multi-methods over the use of case/switch

didibus02:06:49

And even to some extent, protocols, though that won't apply to all pattern matching use cases

nate_clojurians02:06:51

multimethods do seem good for extension, for sure

didibus02:06:11

And I guess Spec now also does some form of pattern matching.

nate_clojurians02:06:49

elixir does end up really brittle when you're passing around values from process to process and matching on data structures, but I can't see how it would be much different if I was using clojure and having to manipulate the datastructures from thread to thread

nate_clojurians02:06:55

yeah, that's true

nate_clojurians02:06:33

I'm gonna pop off for the night, but I've really enjoyed talking about all of this

nate_clojurians02:06:43

have a good night @didibus

didibus02:06:44

Ya, I can't say for sure. I've never worked professionally in a language with pattern matching, only toyed with those. I can't say I ever "miss" it in Clojure, but it could be because I've never used it enough.

didibus02:06:51

Have a good night

seancorfield02:06:35

Pattern matching is really good for a closed set of options where the compiler can help figure out whether you've matched all cases. That's what you get in languages like Scala (and Haskell I think).

seancorfield02:06:08

Given Elixir is dynamically typed, I don't see how it can get that support -- so I can see how it would become brittle without compiler support.

didibus02:06:17

@nate_clojurians For when you wake up... I also wonder if performance was a concern of Rich. You'll come to realize Rich Hickey is quite performance sensitive, and pattern matching has runtime overhead. So that could have been a strong reason to leave it out. Also... it was a choice for Clojure to be a Lisp with powerful extensions mechanisms. It is always okay in my opinion to reach for what you think is best, so if you really want pattern matching, a lot of people use core.match and defun (https://github.com/killme2008/defun) as well in production and what not. So don't be shy. But maybe as a beginner, just to get accustomed, it's better to start practicing ways around it, and later, you can decide if you want it back and use a library.

didibus02:06:48

My understanding in Elixir, and really in Erlang, is that it is actually unification

didibus02:06:25

The = in Erlang is not assignment, but the unification operator

didibus03:06:15

Which to be honest, I never understood the difference between unification and pattern matching ๐Ÿ˜›

alexmiller03:06:37

Rich did investigate pattern matching early on. perf is one key concern (scaling with cases), and the other is the closed nature of the match set. polymorphic systems like multimethods and protocols are both open for external extension.

didibus03:06:04

Sweet, seems like I guessed right ๐Ÿ˜

didibus03:06:38

I also do think it complects binding and flow. For example:

(defun foo
  ([false true] 1)
  ([true false] 2)
  ([true true] 3)
  ([false false] 4))

didibus03:06:20

Versus:

(defn foo [[a b]]
  (case [a b]
    [false true] 1
    [true false] 2
    [true true] 3
    [false false] 4))

didibus03:06:19

Much easier to refactor the destructuring fn in case the structure changes. Say it becomes a map?

(defn foo [{:keys [a b]}]
  (case [a b]
    [false true] 1
    [true false] 2
    [true true] 3
    [false false] 4))
Versus:
(defun foo
  ({:a false :b true} 1)
  ({:a true :b false} 2)
  ({:a true :b true} 3)
  ({:a false :b false} 4))

didibus03:06:35

As I see it, the whole point of destructuring (or one of its big use cases) is decoupling the fn body to the structure of the args. In this case, the body doesn't know how to get a and b from the arg, it's abstracted in the destructuring. But pattern marching reintroduces some of the body's logic back into it.

jyad186612:06:49

Does Intellij/Cursive provide auto complete for Java classes ? It doesn't work for me and I didn't find anything ? ๐Ÿ™„

manutter5113:06:41

There's currently a bug in IntelliJ that's interfering with symbol resolution, maybe that's related? There's more discussion of the issue in the #cursive channel.

jyad186613:06:06

Thanks , I will ask there,

lepistane13:06:53

Are there any cases where tests would run properly from repl if i (run-tests

lepistane13:06:57

but break when i lein test? i get exception ctual: java.lang.IllegalArgumentException: Cannot open <nil> as a Reader.

tavistock14:06:35

differences in dependencies between dev and test

lepistane14:06:33

thanks! i noticed i am missing one

tavistock14:06:52

i use with-profile so i donโ€™t have to repeat deps. like make a :shared profile and start the test with lein with-profile +shared test

suni_masuno14:06:22

Style question, does the & go on the same line or last line in arguments?

(defn
 [a
  & b]

or

(defn
  [a &
   b]

manutter5114:06:42

I put all arguments on the same line. There should be only a few of them anyway.

manutter5114:06:02

(Exception: when youโ€™re destructuring a map with a lot of keys.)

donaldball14:06:19

In long-lived code, I always destructure in a let inside the defn. I have trouble visually parsing fn args that destructure inline.

drewverlee16:06:27

in joy of clojure 2 were shown this code:

(def very-lazy (-> (iterate #(do (print \.) (inc %)) 1)
                    rest rest rest))
;=> ..#'user/very-lazy

(def less-lazy (-> (iterate #(do (print \.) (inc %)) 1)
                    next next next))
;=> ...#'user/less-lazy
However this isn't what i get in my repl:
Clojure 1.10.0
(def very-lazy (-> (iterate #(do (print \.) (inc %)) 1)
                    rest rest rest))
..#'user/very-lazy
(def less-lazy (-> (iterate #(do (print \.) (inc %)) 1)
                    next next next))
..#'user/less-lazy
has next changed?

ghadi16:06:19

iterate has changed, in 1.7.0, I think?

drewverlee16:06:34

i suppose i'm having a hard time demonstrating that rest is more lazy then next.

(next '(1))
=> nil
(rest '(1))
=> ()
Doesn't, to me, demonstrate laziness, there are just different return values. The claim is that rest doesn't force realization of remaining elements, but i can't build an example to demonstrate this.

alexmiller16:06:42

I think it will depend on the seq you're using

alexmiller16:06:06

if you're digging into this area, this (historical) page from around the time of the seq overhaul may be of interest https://clojure.org/reference/lazy

hiredman16:06:17

() vs. nil does actually demonstrate it, but you need have more context, basically nil in this case means there for sure no more elements which means rest has inspected the contents (which is not a lazy operation). next returning () means there may or may not be more elements and next has not inspected the contents

drewverlee16:06:42

Thanks Alex! I'll take a look.

drewverlee16:06:03

"rest has inspected the elements" did you mean next here?

hiredman16:06:19

sorry, yes, swap them around

hiredman16:06:48

I never bothered remembering which is which, I use rest everywhere and call seq any place I want to nil pun

hiredman16:06:36

(rest predates next and used to behave more like next)

hiredman16:06:15

back in the day there was no "empty seq", just nil, and () was an empty list

drewverlee17:06:06

ah, ok. this example makes sense to me: https://stackoverflow.com/questions/15983316/next-and-rest-in-clojure In order to demonstrate the difference you need to make sure some other factor isn't going to eval the body and trigger the side effect (that helps show the lazyness). Here def does that, sense rest-test points at the un examined body as where next-test prints when called with def because it evals the body.

drewverlee17:06:53

i suppose its simpler to use rest everywhere except when your specifically going to use the nil return value to check an exit condition? Joy of Clojure seems to suggest the opposite though ๐Ÿ™‚ > In general, we recommend that you use next unless youโ€™re specifically trying to write code to be as lazy as possible.

alexmiller17:06:52

I tend not to use either of them most of the time

alexmiller17:06:55

instead, think in manipulating colls and seqs most of the time

alexmiller17:06:04

in case of loop/recur, lean on destructuring

alexmiller17:06:27

there can be some very subtle implications if working with walking nth items down via next/rest but you really can ignore that 99.9% of the time

drewverlee17:06:08

far enough. I think i understand them enough now to be aware of when the difference will matter. As you said, i mostly try to work with collections. Thanks again alex!

nate_clojurians18:06:44

@alexmiller @didibus I guess I don't see why pattern matching is even being compared to multimethods and protocols, and why they're mutually exclusive. if I wanted to allow external extension I'd do multimethods and protocols (which elixir has as well), but most times I just want to do different things based on the structure I have (as in my original question) and the structures are used only within a function or namespace

didibus19:06:09

Are you sure Elixir has multi-methods? I thought it only had Protocols?

nate_clojurians19:06:14

I meant to only indicate protocols

didibus19:06:34

Anyways, when you need a quick off the hand conditional restricted to a single function, normal conditional constructs are just fine. The danger is if you're using that same conditional logic over and over in different places, and if you are constantly having to extend it with new cases. That's when multi-methods will shine and be a better tool in my opinion

didibus19:06:39

In your case. It seems what you're interested in is some kind of duck typing conditional. Like, what kind of structure do I have here. And based on that you would branch out.

didibus19:06:33

Multi-methods are great for that. But they assume this will be used a lot. Like if your structures were top level entities of your app. It's overkill for you I agree

didibus19:06:20

With your example, I think it's hard to understand. But in simple cases like yours, there are alternatives like you've seen. The fn overload is one, if you just want to branch on number of elements, it will also perform a lot faster.

nate_clojurians19:06:49

yeah, multimethods do feel like a lot of ceremony for the small case

nate_clojurians19:06:06

I can see how having all the matches everywhere could lead to harder-to-change code

didibus19:06:20

I guess to see the difference, we'd need to have an example of pattern matching and multi-methods applied more to solve an architectural design issue.

nate_clojurians19:06:04

pattern matching comes off so elegant, to me, it's almost grating not to be able to use it, but thinking back to my elixir apps I do end up having to duplicate a lot of structures

didibus19:06:49

I do think pattern marching can make some things simpler and clean and easy to read. I think the reason not to include it in the core was the fear that it would also quickly be used for larger things which multi-methods or protocols are better suited for.

didibus19:06:55

So, another alternative, which I use, when your conditional logic itself is determined by the structure of data, is to use Spec

didibus19:06:52

You can spec the various options using the or spec. And then use conform to know which one it conforms too in order.

didibus19:06:08

The name of the first spec to match will be returned.

didibus19:06:04

It is still more verbose then pattern matching though

didibus19:06:28

Like this:

(first
 (s/conform
  (s/or
   :char (s/tuple any?)
   :char-count (s/tuple any? any?))
  [1 "c"]) 

didibus19:06:05

That will return :char-count

nate_clojurians19:06:22

ahhh interesting

didibus19:06:24

If you pass it ["c"] it will return :char

didibus19:06:57

Now you can imagine having a multi-method whose dispatch function is the above conform code.

didibus19:06:14

Maybe you call it: decode

didibus19:06:09

Still overkill in your case. But for a more complicated case it could be quite nice

nate_clojurians19:06:10

or just (if (= 1 (count my-vec)) [1 (my-vec 0)] [(parse-int (my-vec 0)) (my-vec 1)])

nate_clojurians19:06:05

I do like the function arity dispatch thing I did

didibus19:06:06

I mean in your case, I think function overloading was pretty nice

nate_clojurians19:06:15

agreed ๐Ÿ˜„

nate_clojurians19:06:35

I think I'm starting to see why they're being compared now

didibus19:06:39

After that, a conditional on count is probably second best, like with if or case if you have more then 2 branches

nate_clojurians19:06:03

yeah, case with (count my-vec)

didibus19:06:09

Ya. So I'm not gonna lie. Pattern matching in your situation also seem quite nice. I think for a lot of small simple algorithms, especially with recursive conditional branches, pattern matching is really good. And that's probably its sweet spot

didibus19:06:22

And if you're doing a lot of these (and aren't trying to learn Clojure but know it well already ๐Ÿ˜‰), it could make total sense to reach for core.match and defun in that scenario

didibus19:06:43

Well, unless you cared about performance

nate_clojurians20:06:04

I'm coming from ruby, performance is relative ๐Ÿ˜œ

nate_clojurians18:06:01

do people reach for multimethods and protocols in the case I shared last night?

nate_clojurians18:06:25

(defn- split->cnt+char
  ([char]
   [1 char])
  ([cnt char]
   [(parse-int cnt) char]))

(defn- encoded-string->cnt+chars
  [encoded-string]
  (->> encoded-string
       (re-seq #"\d?\D")
       (map #(apply split->cnt+char (cstr/split %1 #"")))))

nate_clojurians18:06:44

I just wanted to do different things based on whether the vector was two elements or one element

alexmiller18:06:41

I feel like I don't commonly encounter this situation

nate_clojurians18:06:52

that's fair, it might be a weird situation

nate_clojurians18:06:59

run length decoding is an odd one

alexmiller18:06:08

instead of applying, you could just destructure

nate_clojurians18:06:01

I had something like that, but then the variable names get a little weird. like if I mapped them I'd have [cnt char] but if char is nil then cnt is actually the char

alexmiller18:06:38

yeah, usually the more optional part is on the right so it's not weird

nate_clojurians18:06:55

I could reverse them first? ha

nate_clojurians18:06:11

I see some pattern matching macros for clojure but I'm going to try not to make clojure into erlang/elixir and just try to solve problems clojure-y ways

lilactown18:06:07

@nate_clojurians is there anything stopping you from having the function return char+cnt instead of cnt+char?

lilactown18:06:21

I think that would solve this problem from a syntax POV

nate_clojurians18:06:42

the encoding itself is cnt+char

nate_clojurians18:06:59

I'm just going through exercism with clojure and I hit the run length encoding exercise

nate_clojurians18:06:13

"AABCCCDEEEE"  ->  "2AB3CD4E"  ->  "AABCCCDEEEE"

nate_clojurians18:06:08

so when you split up "2AB3CD4E" with that regex you end up with something like [["2" "A"] ["B"] ["3" "C"] ["D"] ["4" "E"]]

nate_clojurians18:06:01

and so I'm going through and left-padding the single element vectors with 1 and then parsing the first elements from the two element vectors to integers

nate_clojurians18:06:42

then I can run through and repeat and concat

nate_clojurians18:06:53

and then go back and combine steps where it makes sense

nate_clojurians19:06:49

so I could make it char+cnt but I'd have to reverse each inner vector or do some regex magic or something

lilactown19:06:25

yeah. I think what youโ€™re doing now seems best ๐Ÿ˜„

nate_clojurians19:06:39

I'm probably fixating on too minute a problem but, unfortunately, this is how I learn ยฏ\(ใƒ„)/ยฏ

nate_clojurians19:06:36

I end up learning stuff thoroughly, though, so that's nice

lilactown19:06:09

youโ€™ve uncovered a small shortcoming in Clojureโ€™s default ability to express case matching. it doesnโ€™t have as good of support for positional matching as, like you said as an example, Elixir

lilactown19:06:21

thatโ€™s good knowledge!

lilactown19:06:43

if youโ€™re doing something that involves a lot of positional matching (like writing a parser/lexer) then you now know that youโ€™re going to need some extra batteries if you decided to do it in Clojure

nate_clojurians19:06:03

for sure, I would expect that now

nate_clojurians19:06:26

and next time I come across the issue in the small case I know not to fixate on better ways to solve it, it's just kind of less than ideal

nate_clojurians19:06:42

every language has that stuff, just move on ๐Ÿ™‚

lilactown19:06:58

yes ๐Ÿ˜„ hopefully you also get to use some of Clojureโ€™s stronger features like itโ€™s rich polymorphic dispatch via multimethods and protocols

lilactown19:06:43

they work a lot better when combined with more descriptive data like maps and types/records

nate_clojurians19:06:11

yeah, for sure. I've learned those but I haven't yet come across a use case where I knew I wanted to use them. the problems I'm working on are pretty small though

lilactown19:06:07

yeah. I find in applications, I tend to reach for multimethods. When writing a library, I tend to reach for protocols

cakir-enes19:06:07

Hey, can you suggest a Clojure repo that can be considered as "idiomatic"/well written? I know 101 level clojure and I want to study a repo to see if I'm actually going to like the language

alexmiller19:06:49

many people recommend weavejester's as great example code

cakir-enes19:06:37

Is there any type of problem/task to solve that helps appreciating clojures stronger sides compared to other generic langs?(idk if this is good question tho)

drewverlee19:06:06

Clojure runs on the JVM, so it has the same pros and cons as the JVM. As a language, it has the same abilities as any other mainstream language. I would say the two of the stronger selling points are 1. code is data : which gives you the ability to pass functions as arguments and return them. 2. immutability as default: which means that hard to reason about state transitions become the exception and not the norm. I migrated from python -> ruby -> clojure and it's clear to me that everything i found useful in those languages has its roots in LISP and everything else ...

seancorfield19:06:40

"code is data" is something else. First class functions/higher order functions is what gives you the ability to pass functions as arguments and return them.

seancorfield19:06:18

"code is data" is what is behind macros: the ability to operate on Clojure code as a data structure -- transforming code prior to evaluation.

cakir-enes19:06:56

Yep, I've heard those a lot but havent had much mind-blowing moments tho.

seancorfield19:06:25

Lots of languages provide 1st class fns and higher-order fns these days -- you can even get most of that in Java now ๐Ÿ™‚

cakir-enes19:06:27

Code is data implies macros and ability to manipulute directly eval tree I guess

seancorfield19:06:06

And macros are not anywhere near as "popular" in production Clojure code as many people would seem to think, when they first start learning about macros ๐Ÿ™‚

seancorfield19:06:40

For example, from our code base at work

Clojure source 279 files 66902 total loc,
    3118 fns, 641 of which are private,
    422 vars, 29 macros, 60 atoms,
    580 specs, 21 function specs.

seancorfield19:06:05

(that's not including 20k lines of test code, but we don't track macros in that)

seancorfield19:06:17

Given that Clojure is, by design, a "general purpose language", I think it's hard to point to a specific type of problem/task that shows Clojure's strengths.

seancorfield20:06:51

It's great at data manipulation/transformation. It's great at concurrency (in various forms). Perhaps the real "killer aspect" of Clojure is a good REPL-driven development workflow? It's incredibly productive because of that extremely tight feedback loop.

cakir-enes20:06:16

Maybe its strong side is, since LISPs can look messy easily it forces me to write more fns, break-down the problem. Which is usually a good thing

samedhi20:06:19

REPL development (both using a actual repl or with immediate feedback using fighweel) is a big part of the "fun" here. It is hard to describe using toy examples since toy examples are usually read (not written) by those "outside" the clojure community.

didibus23:06:03

I'd say Clojure excels at big boring enterprise apps

didibus23:06:12

Just like Java does ๐Ÿ˜‹

didibus23:06:21

Except Clojure makes working on those fun again

didibus23:06:44

So ETL processes, data pipelines, micro-services, SOA, etc.

didibus23:06:49

Clojure is best when your state is inside a data store like S3, Mongo, MySQL, a file on disk. etc.

didibus23:06:37

And your logic is mostly getting data, transforming it, putting it back, dispatching events to some other services, rinse and repeat

didibus23:06:30

That makes it a really good choice as a backend for SPA apps as well

didibus23:06:17

Which is where ClojureScript comes in handy, cause it excels at front end SPA

didibus23:06:09

Clojure isn't so great for short lived programs, like grep, or other one shot quick command line tools

didibus23:06:41

Or for high performance low level code, like graphics engine, audio processing, AAA games, etc.

didibus23:06:46

Which is kind of unfortunate, because Clojure is better for big applications, but for small weekend projects, which is what people often play with first, it doesn't really shine. Though its definitely still good. But you won't really understand the benefits in those scenarios

seancorfield00:06:27

Which all puts me in mind of my Clojure/West talk from many years ago "Doing Boring Things with an Exciting Language" ๐Ÿ™‚

didibus02:06:09

Haha, definitely, when the problem is boring, make your tools more exciting

didibus02:06:22

It's also why I feel it's good to complement Clojure with one or two more languages, if you want to cover all possible use cases.

didibus02:06:51

A lot of people complement it with Rust for low level high performance things.

didibus02:06:31

I think to some extent OCaml can be used for this as well, or Go.

didibus03:06:10

But these will also hit a limit. So I feel Rust really covers the other half of Clojure. Or Nim, C, C++ as well.

didibus03:06:31

For scripts that don't need high performance, you have ClojureScript or Joker which work very well, and are easy to pick up as they are mostly Clojure. You can even use Clojure most of the time. Like, if you don't need uttermost performance, you can generally also accept the slower start time of Clojure

seancorfield03:06:23

Almost everything I do is server-side, long-running processes, so "all Clojure" is fine ๐Ÿ™‚

didibus03:06:21

The last bit I think is not covered is Android and iOS development. ClojureScript works for that though

seancorfield03:06:29

But I try to follow Pragmatic Programmer advice of learning a new language every year or so... Last year was Kotlin (quite nice). Go was several years ago (disappointed). Rust was in between (liked it, but I don't do any low-level stuff these days).

didibus03:06:32

Haha, ya me 2

seancorfield03:06:58

Elm was another fun language to learn (several years ago).

didibus03:06:10

But I like the thought that I've got all cases covered.

didibus03:06:20

My next ones that I'm interested in strongly are Rust, Rebol, OCaml

didibus03:06:10

Have you ever heard of Fantom ?

seancorfield03:06:27

Rust is fascinating. I haven't looked at Rebol. I did look at OCaml years ago (but ended up learning F# instead!).

didibus03:06:27

I actually really like that language. But it is so unpopular

didibus03:06:20

Rebol looks really interesting from a meta programming level

didibus03:06:46

I think it might win as the next language I want to try

seancorfield03:06:52

Heard of Fantom. Never looked at it.

didibus03:06:16

It's basically taking DSLs to the extreme

didibus03:06:30

Oh Fantom. Ya

didibus03:06:42

Its like what I'd want Java to be

didibus03:06:34

It's like how I find C# to be a better attempt at Java, and then I find Fantom to be a better attempt at C# :p

didibus03:06:36

Though... like Go, it only has generics for bundled data structures

seancorfield03:06:49

Have you tried Kotlin? That does a good job of being a "better Java".

didibus03:06:21

I never tried it no. But I've had to browse Kotlin source to debug things before

didibus03:06:08

But my first impressions weren't that great to be honest

didibus03:06:04

I feel a little similar to how I feel about Scala. It tries and carve that weird middle of almost FP, but not really, almost hindley Milner, but not really

didibus03:06:23

And I'm not sure I like it

seancorfield03:06:05

I did Scala for about 18 months for work. I liked it at first and gradually grew to like it less and less, the more I worked with it. ๐Ÿ™‚

brandon.ringe21:06:24

Hello. I'm wondering if someone can point me in the right direction to correct this log format issue. I'm using timbre for logging on Windows, and when I log an exception with timbre, I see all these characters that I think are ascii encoding or something? Is there something I can use to make the printing look as it should?

robertfrederickwarner21:06:31

My first line of attack would be to see if you can get a terminal on windows that supports ansi colour codes, but that's outside my knowledge - if you can't do that, you can turn off timbre colour codes for stacktraces. see https://github.com/ptaoussanis/timbre/#disabling-stacktrace-colors

robertfrederickwarner21:06:20

what are you running your repl in?

brandon.ringe21:06:22

Ah thanks. Tried powershell and cmd

dpsutton21:06:36

do you have git bash laying around?

noisesmith21:06:53

you can turn off aviso.pretty, that's what colorizes

brandon.ringe21:06:22

Ah I see. I may take that route. I do have git bash. I can try that too

brandon.ringe21:06:10

Update: I found on stackoverflow that: >in latest Windows 10, you can enable ANSI in conhost via the following reghack -- in HKCU\Console create a DWORD named VirtualTerminalLevel and set it to 0x1; then restart cmd.exe.

brandon.ringe21:06:15

This worked for me ๐ŸŽ‰

penryu23:06:23

@brandon.ringe Alternatively, maybe check the TERM value (or any cmd equivalent) to see why it thinks your term supported ANSI when it apparently didnโ€™t. Otherwise your logs might get littered with ANSI escapes too.