Fork me on GitHub
#beginners
<
2019-06-10
>
phrsantos01: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

phrsantos02: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 ... }

phrsantos02: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.

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)))

lilactown01:06:02

in what way?

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

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)

didibus02:06:44

Destructuring is not pattern matching

didibus02:06:39

I think Rich Hickey has an issue with pattern matching

didibus02:06:50

Because it complects two things

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

seancorfield02:06:47

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

thanks2 4
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

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.

didibus02:06:50

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

didibus02:06:05

What do you mean by conditional logic that accesses?

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 😛

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.

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.

didibus02:06:59

normally closed*

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

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

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 @deleted-user?

seancorfield02:06:43

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

didibus02:06:55

Okay, that's what I remembered

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

didibus02:06:11

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

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

@deleted-user 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 😛

Alex Miller (Clojure team)03: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.

☝️ 4
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.

Yadia12:06:49

Does Intellij/Cursive provide auto complete for Java classes ? It doesn't work for me and I didn't find anything ? :face_with_rolling_eyes:

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.

Yadia13: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.)

8
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.

Drew Verlee16: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?

Drew Verlee16: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.

Alex Miller (Clojure team)16:06:42

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

Alex Miller (Clojure team)16: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

Drew Verlee16:06:42

Thanks Alex! I'll take a look.

Drew Verlee16: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

Drew Verlee17: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.

Drew Verlee17: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.

Alex Miller (Clojure team)17:06:52

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

Alex Miller (Clojure team)17:06:55

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

Alex Miller (Clojure team)17:06:04

in case of loop/recur, lean on destructuring

Alex Miller (Clojure team)17: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

Drew Verlee17: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!

deleted18: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?

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.

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.

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

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

didibus19:06:06

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

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

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

Alex Miller (Clojure team)18:06:41

I feel like I don't commonly encounter this situation

Alex Miller (Clojure team)18:06:08

instead of applying, you could just destructure

Alex Miller (Clojure team)18:06:38

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

lilactown18:06:07

@deleted-user 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

lilactown19:06:25

yeah. I think what you’re doing now seems best 😄

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

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

lilactown19:06:07

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

ec19: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

☝️ 4
Alex Miller (Clojure team)19:06:49

many people recommend weavejester's as great example code

ec19: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)

Drew Verlee19: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.

ec19: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 🙂

ec19: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.

ec20: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. 🙂

bringe21: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?

robertfw21: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

robertfw21:06:20

what are you running your repl in?

bringe21: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

bringe21:06:22

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

bringe21: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.

bringe21: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.

👍 4