Fork me on GitHub
#beginners
<
2019-07-23
>
Sam Ferrell05:07:37

i think i already know the answer to this, but are there any conventions or mechanisms for organizing clojure code in the same file?

Sam Ferrell05:07:15

like, there's no way to define multiple namespaces in the same file, i assume

Radek07:07:52

Hi everyone! What is the idiomatic use of loop? I mean, where would you use this instead of just calling the function recursively itself?

Gatuque06:07:39

Every time you call a function you allocate a new frame to the stack and might run into StackOverflowError but loop-recur mechanism uses tail call optimization which creates only one frame during the whole recursion

John Collins07:07:03

Typically you prefer loop because it doesn't consume the stack. Clojure does not have tail call optimization, so to compensate it includes the loop/recur macros that convert recursive looking forms into loops. Now, the target of recur can be the 'current' function, but often it's inconvenient to have to write a whole function form to create a possible recur context. The loop is useful for this.

Radek07:07:56

Aaah, I see—so does that mean the preferred way of writing recursive functions in Clojure in general is to use loop/recur? I’m playing with the language for some time now and have mostly seen calling the function itself by name or using recur, that’s why I’m surprised

John Collins07:07:11

Depends on the problem. In some cases, it's cleanest to recur to the function callsite, in other cases it's cleaner to recur to a loop target. In general, it's best to avoid recursive calls unless you're returning a lazy sequence, which will produce values without consuming stack.

John Collins07:07:39

there's a nice, short talk on loop/recur here, which covers some of it's details, as well as its weaknesses. Hopefully some of these problems will be addressed in a future clojure release: https://www.youtube.com/watch?v=SJmK1R0ADnc

Radek08:07:02

Will put this on my watch list, thank you John! 🙂

FiVo10:07:00

I am using reagent/re-frame to build some FE. After a while of development my browsers starts to eat up all the memory. What is usual the issue here?

Crispin12:07:53

Use your memory profile dev tools to capture a memory profile and see where the RAM is being used

FiVo10:07:25

I use live.js to automatically update the page.

Mario C.15:07:23

One problem I keep running into is not being able to decide what the best way of writing a piece of code. For instance, I have a date that I need to get into the YYYY-MM-DD format from a regular UTC time stamp. I came up with 3 different ways to do this with each IMO having their pros and cons.

Mario C.15:07:37

The first one is more descriptive and I believe tells you exactly what I am doing.

(->> (-> decision :updated_at tco/from-string)
                          (tf/unparse (tf/formatters :date)))
(the nested threading I am not too fond of tho)

Mario C.15:07:09

The second and third one seem straight forward

(first (clojure.string/split updated-at #"T"))

Mario C.15:07:18

(re-find #"\d{4}-\d{2}-\d{2}" updated-at)

Mario C.15:07:14

I dont like that I am calling first on the second one

Mario C.15:07:09

My question is whats the better way of doing this? Perhaps there is a better way?

jthibaudeau15:07:43

I am pretty sure you can avoid nesting thread in this case

(->> decision
     :updated_at
     tco/from-string
     (tf/unparse (tf/formatters :date)))
I didn't test it but I think it should work

Mario C.15:07:01

It does work

Mario C.15:07:14

I think I am going to go with this way instead

jthibaudeau15:07:30

personally I find it most readable

Mario C.15:07:41

Yea I agree. Plus if the format of date changes then I have to remember to come inside this function and change the regex. Whereas in this case it should handle most changes.

Alex Miller (Clojure team)15:07:29

I think the first one is the best solutino for "parse", but maybe put (tf/unparse (tf/formatters :date)) in it's own function to remove the nesting

Mario C.15:07:09

After some thinking I think the first one is the better solution. Its less brittle IMO

Ashley Smith15:07:49

so I'm making a program that is going to have a server and a client and some sort of database for the server to communicate to. I want to use vagrant and ansible to develop locally for now. Is having two programs under one project as simple as dividing the code base between server and client, or do I have to do special things in deps.edn to build server code and client code?

noisesmith17:07:31

deps.edn doesn't even build - in this case do you mean constructing separate classpaths or a separate build tool?

noisesmith17:07:14

I would assume you'd have one deps file for the client process, and another for the server

Ashley Smith17:07:11

This is kind of what I was getting at, I'm very new to all this so I'm trying to get the 'hello world' of setups with ansible and deps.edn, but struggling to figure it all out you know?

noisesmith17:07:01

deps.edn is a tool for constructing a classpath, so I assume it would be useful for the ansible build for that specific usage. Often when packaging people want to set up a persistent cache of external artifacts on CI, and point the dep resolution to that cache

Ashley Smith17:07:06

can I ask, what is a classpath?

noisesmith17:07:55

in the jvm, the classpath is the list of locations used for finding resources, code, or class files

Ashley Smith17:07:40

okay, and deps.edn is a way of saying 'here's a package, heres a list of files it brings to the table so use these

Ashley Smith17:07:43

and stuff like that?

noisesmith17:07:56

what deps.edn (or lein, or boot) do is take a list of dependencies, resolve their tree of transitive deps, download all the ones you don't have already to a cache, then point your java classpath to all those artifacts

noisesmith17:07:06

it's not unusual in my experience for a clojure program in production to have ~20 explicit deps that are used directly, and hundreds of deps total once they are transitively resolved

noisesmith17:07:57

in a running clojure program you can see your starting classpath with

> (System/getProperty "java.class.path")

Ashley Smith17:07:29

Yeah my haskell programs had lots of dependencies

Ashley Smith17:07:55

the reason I'm a little confused is because I've never done server/client applications in any language so I'm learning the theory and the practice slowly

noisesmith17:07:55

right - so in theory your server and client could be 100% independent codebases and share nothing but an implicit agreement to use the same resource paths on some protocol

noisesmith17:07:42

in practice, with clojure, one advantage is we can share the same data functions and configs between front end and back end code, to simplify that distributed interaction

noisesmith17:07:21

(of course you can do this in any language if the server and client use the same runtime environment, but with clj / cljs we can do this with java backend and javascript browser client)

Ashley Smith17:07:35

I don't mind having a server and client folder to seperate the codebases, but it would be nice to keep it all under the same project I guess?

Ashley Smith17:07:57

at least until the client gets too big it should split off

noisesmith17:07:47

are they server and client both jvm clojure? is it clj / cljs?

Ashley Smith17:07:20

well, I haven't made anything yet. Right now I have a hello world reagent app

noisesmith17:07:21

those were two different options

noisesmith17:07:31

oh, clj / cljs OK

Ashley Smith17:07:32

The client will be cljs

Ashley Smith17:07:41

and the server clj I presume

Ashley Smith17:07:28

The goal today is to just have some data on the server, just a string, and simply get it sent to the client to be displayed. Then stage two would be storing that in a database and learning how to get info out the db to the server, then serve it to the client

Ashley Smith17:07:39

I don't want to do much, I just want the basic knack down and go from there

noisesmith17:07:05

in my experience this structure was good: src/clj/foo/bar - everything under this path is server code, src/cljs/foo/bar - everything under this path is client src/cljc/foo/bar - everything under this path is code used by both client and server (custom data logic that both ends want, in particular)

noisesmith18:07:00

for example a data validator would go in src/cljc so that client and server would implicitly agree on data format

noisesmith18:07:07

with deps.edn I'd use one deps file to build deps for the jvm process that compiles the cljs to js, and another to build the deps for the server process

noisesmith18:07:22

with leiningen it's easier to put all of this in one project file

Ashley Smith18:07:57

I'd probably write server / client to make it easier for me but I take your point

Ashley Smith18:07:25

Where would the location of these edn files go?

noisesmith18:07:30

there's a strong convention to using clj / cljs / cljc as directory names

noisesmith18:07:42

consider that you could also end up with java / js dirs

Ashley Smith18:07:46

I did some work on braid.chat and server/client is how they were

noisesmith18:07:28

that's fair, but breaking it up by file suffix simplifies things in the case where cljc can be loaded by both cljs and clj

Ashley Smith18:07:26

It talks about setting up two roles, java and lein. how exactly do the two differ other than one is using a REPL and the other is just the program?

Ashley Smith18:07:50

Or will they mostly do the ssame things? (I'm just thinking, how I'm going to do this when I'm not using leiningen so)

noisesmith18:07:07

lein is a build tool - it constructs a class path, runs dev tasks, builds artifacts, etc.

noisesmith18:07:33

java is the vm that actually runs clj code (even lein uses java to carry out the task you ask it for)

noisesmith18:07:46

so in your case, you are trying to replace lein with deps.edn

noisesmith18:07:22

you'll need to find some replacement for the lein uberjar task (which constructs an artifact to deploy), but otherwise deps.edn can construct a classpath and run a repl

Ashley Smith18:07:10

okay thats great, so while the building the artifact isn't something I can do, I can still replace lein (I know I can do a repl so hopefully I can hook this into that?)

noisesmith18:07:02

well lein is doing multiple roles, and the premise of deps.edn was to specialize away from that - so you've replaced some lein usecases but not all the lein usecases in that blogpost

noisesmith18:07:03

oh wait - that blog post is doing a bad thing - using lein to run a server process :/

noisesmith18:07:20

which is good news for you because deps.edn is actually OK for running a server, lein isn;t :D

Ashley Smith18:07:29

ahaha woohoo 😛

noisesmith18:07:13

I assumed they would be using lein to build an uberjar - that's the sensible way to do ops with lein, but they are instead using the build tool to run the process in prod

noisesmith18:07:34

it's the equivalent of shipping source + Makefile to your server, then running gcc before startup :P

Ashley Smith18:07:09

im still trying to get my head around roles.. so, a role can do tasks, and sometimes you can have a common role to do common tasks. But in this post, the lein role just installs leiningen.. I don't really see where the magic happens

noisesmith18:07:46

it isn't used to run leiningen?

Ashley Smith18:07:50

its tough because this tutorial is working with code thats already written

Ashley Smith18:07:34

---
# config/roles/lein/tasks/main.yml
# Install leiningen

- name: Copy lein script
  copy: src=lein dest=/usr/bin/lein owner=root group=root mode=755

Ashley Smith18:07:25

They store a copy in the configuration and just copy it over, so I'm not really sure where in this post it starts the server? Like that's what I'm looking for at least

Ashley Smith18:07:45

by the way thank you for talking about this with me I should have asked rather than bombard you with questions like I have 😞

noisesmith18:07:50

nothing here looks specific to that codebase though: no matter how you code your app, you will end up running -main in some namespace to start a service

noisesmith18:07:30

oh, I accidentally answered your question above: the convention is for program entry points to be named -main, so that is what starts the service

noisesmith18:07:12

you can pass an argument to clojure.main to start the -main in a specific ns instead of a repl, and this is how one runs a clojure service if they aren't crazy like whoever put together this blogpost

noisesmith18:07:07

there's a few reasons not to use lein to run on a server: lack of concrete redeployable artifact, useless parent process that starts up only to create your classpath, the wrong jvm settings for running a server

noisesmith18:07:39

using deps.edn is luckily better behaved (but I'd still recommend making an uberjar or similar that allows recreating a specific deploy)

Ashley Smith18:07:45

okay.. I'm trying to connect the dots as there's a lot of buzzwords but not much connection, or at least, its not so clear to me. Vagrant fires up some virtual machines. You give it a file name as a playbook which is a set of tasks given to a set of roles. Why is there a need for the leiningen role if it is just a single task. I assume when you switch from local to a server, you would edit your playbook and change the role from lein to java, but I'm trying to connect the playbook to the tasks

noisesmith18:07:41

NB: often you'll get a much better deploy setup with clojure itself by finding the instructions for setting up a java project, then using an uberjar using clojure.main + your source where they would use a java fat jar

noisesmith18:07:31

I know nothing about ansible roles - based on how they cargo cult the clojure stuff I wouldn't be surprised if they were using ansible weirdly too

Ashley Smith18:07:46

So in his example, common role has a task called Install motd. I can see it, it places some data somewhere I assume for the program to grab. However, the playbook is also a list of things. What are these things? So, the only one I have is named Apply common configuration and looks like this:

---
# ansible/provision.yml
# Provision development environment

- name: Apply common configuration
  hosts: all
  sudo: yes
  roles:
    - common
So, to all the hosts in the vagrant the common role is.. applied? And in doing so all of common's tasks are completed automatically?

Ashley Smith18:07:03

I might actually go and write my own tutorial after I crack this as without a background in networking it's not so easy to get into this

noisesmith18:07:16

maybe someone else here knows - or maybe someone on an Ansible forum

Ashley Smith18:07:34

Yeah. Unfortunately there's no ansible channel as its not a clojure thing

Ashley Smith18:07:41

but I'm going to need to ask further

Ashley Smith18:07:47

thank you for your time though ❤️

noisesmith18:07:14

I mean - maybe they have their own slack or IRC or whatever - big parts of what you need to figure out are not clojure related

Ashley Smith18:07:42

yeah thats the struggle

noisesmith18:07:24

it should be possible to break this up into domain specific parts: a generic ansible setup with a spot for "construct the artifacts your program needs on deploy" - an ansible expert could help there

noisesmith18:07:36

then a clojure expert can tell you what goes in that spot

Ashley Smith18:07:17

Finding itweird that it's going to require an expert to show me these things, I'm going through the ansible docs for a bit I think

noisesmith18:07:09

I think one systemic issue is that at most one person needs to sort this out for a given project in most cases, and the task is done once per project and rarely needs to be updated

noisesmith18:07:26

so there's a lot of "barely familiar" people, and few who really know it well

Ashley Smith18:07:19

I get that.. Hopefully I can get the hang of it too so I can help others

Ashley Smith18:07:30

A role isn't what I thought it was. A Role is a self-contained playbook, so a playbook can contain tasks as well as the roles, and the reason I didn't see the tasks get called before is purely because they're called not by the playbook but by the role

Ashley Smith18:07:37

The role isn't a category but an active thing

bartuka16:07:27

Hi, i'm using the clj-http library to perform a POST request and I'm receiving this error

bartuka16:07:32

1. Unhandled javax.net.ssl.SSLException
   Received fatal alert: record_overflow

bartuka16:07:56

the payload is no that big in order to reach some kind of overflow, however I imagine that this is the case

bartuka16:07:02

can I increase this limit?

dpsutton17:07:31

i googled this error and it seems to be related to teh default tls tranpsort on java 1.8

ghadi17:07:48

I'd make sure you're on a recent JDK1.8 build

ghadi17:07:17

the record_overflow refers to a tls record on the wire, not the HTTP req

bartuka17:07:44

I'm using openjdk version "11.0.3" 2019-04-16

bartuka17:07:08

and I changed the tls defaults, but did not have any impact

ghadi17:07:33

@dpsutton that link seems unrelated

ghadi17:07:04

@iagwanderson there have been a few issues with the TLS1.3 stack on 11 -- some fixed in 11.0.4

ghadi17:07:03

I would look into either disabling TLS1.3: SslContextFactory sslContextFactory = new SslContextFactory(); sslContextFactory.setExcludeProtocols("TLSv1.3"); or bumping to 11.0.4

bartuka17:07:07

@ghadi do you have any idea of how to disable the protocol on the clj-http library? I've beein searching for this

ghadi17:07:12

when you changed the TLS defaults previously, what did you do?

ghadi17:07:57

setting ‑Djdk.tls.client.protocols="TLSv1.2" at the JDK level might be enough

bartuka18:07:11

I changed it using (System/setParameter ...)

noisesmith18:07:54

setProperty ?

ghadi18:07:59

but which parameter and which value?

bartuka18:07:15

sorry, I was afk.. (System/setProperty "https.protocols", "TLSv1.3")

bartuka18:07:22

that's the correct fn

bartuka18:07:02

when I use it, I still have the same problem

bartuka18:07:39

with TSLv1.2 too

bartuka18:07:37

but I still don't get how this is associated with the size of my payload. If I send less items in the request, I get no errors

ghadi18:07:03

that's not the right option

ghadi18:07:21

try the one I pasted above -- and you want to disable TLSv1.3

ghadi18:07:25

not enable it

ghadi18:07:58

https.protocols controls a code path that clj-http doesn't go through

bartuka19:07:38

it worked here! thanks

sova-soars-the-sora23:07:54

I' getting a really strange error running my starter project here, from lein new compojure to tryna throw in HTTP-KIT...

sova-soars-the-sora23:07:58

xception in thread "main" Syntax error macroexpanding clojure.core/ns at (clojure/tools/namespace.clj:15:1). Call to clojure.core/ns did not conform to spec.

noisesmith23:07:57

sounds like your template used an old version of clojure.tools.namespace

noisesmith23:07:05

(or pulled one in transitively)

noisesmith23:07:19

@sova check the output of $ lein deps :why org.clojure/tools.namespace

sova-soars-the-sora23:07:40

` [ring/ring-devel 1.1.8] [ns-tracker 0.2.1] [org.clojure/tools.namespace 0.1.3]`

noisesmith23:07:15

yeah, that's way old - if you explicitly ask for 0.2.11 before ring-devel in the deps list that error will go away

❤️ 4
noisesmith23:07:35

(there might be a newer version still, but I have proof locally that 0.2.11 does not have that error)

sova-soars-the-sora23:07:33

how do you do the voodoo that you do

sova-soars-the-sora23:07:40

XD tell me more about :WHY

noisesmith23:07:02

it's a subset of lein deps :tree focusing on a specific dep

noisesmith23:07:13

you can check out lein help deps to see more

sova-soars-the-sora23:07:20

neato. lein deps 🌴

noisesmith23:07:43

there's also :plugin-tree and :tree-data

sova-soars-the-sora23:07:58

Who are you, who are so wise in the ways of science? Arthur, King of the Britons! /holygrail

sova-soars-the-sora23:07:26

thanks for your lein help

noisesmith23:07:28

haha - just a person who'se been on various clojure channels for a long time