Fork me on GitHub
#beginners
<
2021-08-08
>
NoahTheDuke05:08:43

is there a better idiom of “move n parts from vector 1 to vector 2?” than:

``````(let [[drawn remaining] (split-at 5 (get-in game [:corp :deck]))]
(-> game
(update-in [:corp :hand] #(into [] (apply conj % drawn)))
(assoc-in [:corp :deck] (into [] remaining))))``````

I'm assuming there's a shuffled deck, from which a hand of 5 is drawn from the top. If that is the case, we can do:

``````(let [num-cards 5
deck (-> game :corp :deck)]
(-> game
(assoc-in [:corp :hand] (take num-cards deck))
(assoc-in [:corp :deck] (drop num-cards deck))))``````

Is there a reason the deck must be a vector?

seancorfield06:08:07

I think @UEENNMX0T’s original code adds the five drawn cards to the existing hand -- your code would replace it.

☝️ 3
3
seancorfield06:08:15

I think `(update-in [:corp :hand] into drawn)` is all that is needed?

seancorfield06:08:33

I'll echo @U051MHSEK’s question about the vector representation -- does it need to be ordered? That would simplify things.

NoahTheDuke12:08:03

The deck has to stay ordered. This is a game like Magic the Gathering where effects can reveal cards from the top or bottom or order cards before they’re placed inside etc

NoahTheDuke12:08:58

And in both sides, I want to avoid any laziness because these changes should happen immediately

NoahTheDuke12:08:49

But either way, `into drawn` looks like it will maintain the hand’s original type, so thanks @U04V70XH6

Ick, I misread the code... `into drawn` is right. I don't think lazy sequences will be problematic though, in pure game logic.

NoahTheDuke13:08:11

That’s very possible. I have been bitten by laziness not doing what I expect a couple of times and am now probably unnecessarily wary of it

Effects and lazy operations don't mix well at all... `(map ignite-rocket (lazy-seq [rocket1 rocket2 rocket3]))` . The problem here isn't the lazy-seq, it's that map is lazy.

Joe09:08:56

Possibly a very dumb question: I’m writing a clojurescript/reagent app, and I want to have persistent state across sessions. In clojure I can just spit and slurp to disk, or have a database. But googling around I don’t see a way to easily do either of these from the react app itself. I could create a light web server which will take care of this, but that seems quite heavy for what I want to do. Is there a conventional way to do this without creating a second program?

Joe09:08:00

Interesting, thank you!

Joe10:08:44

Presumably this wouldn’t suitable for the use cases where, say, you would normally use a SQLite database though?

dgb2310:08:35

You can embed SQLite into the browser since it compiles to WASM without any hiccups

dgb2310:08:59

But yeah, this isn’t durable in any way. For example Safari has an opt-out setting that will clear LocalStorage every couple of weeks.

dgb2310:08:23

Here’s what I’ll suggest: If you’re unsure, just use LocalStorage and get going. It’s a very minimal time investment and doesn’t require any kind of server op. As soon as you need a server, add that and think about ops. YAGNI

Joe10:08:29

That sounds like a plan, thanks!

javi12:08:22

https://github.com/alandipert/storage-atom makes it easy to use local storage.

👍 3
alpox17:08:56

And there is https://github.com/replikativ/konserve for a k/v interface to IndexedDB

Jelle Licht11:08:42

Provided I have some Java interface IFoo, which extends an ILifeCycle with `onInit`, `onStart` etc etc which will be called at the appropriate time by some runtime framework; what would be an idiomatic way to not run everything via callbacks (in callbacks in callbacks)?

Jelle Licht11:08:35

I was thinking promises or a once-and-done channel, but have not seen a lot of chatter on working with callback-oriented java apis

emccue13:08:23

I mean, if the runtime system is callback oriented its callback oriented

emccue13:08:26

you can push messages into a queue, sure, but that just turns a "callback api that might be synchronous" into a definitely asynchronous thing

emccue13:08:26

maybe there is a better answer with knowing more specifics of the code, but based on your description it doesn't sound like it

didibus02:08:00

You can make that blocking again if you want.

``````(let [init (promise)
start (promise)]
(.onInit life-cycle #(deliver init %))
(.onStart life-cycle #(deliver start %)))``````

emccue02:08:43

that presumes that they are the ones calling .onInit - which would be sync regardless if they are making a blocking call

didibus02:08:51

I mean register a callback that delivers to a promise. Now your code can just block on the promise.

didibus02:08:37

I'm assuming `.onInit` here is not the call to call the callback, but how to register a callback for the onInit event.

didibus02:08:37

Now in your code you can do:

``````(do-some-stuff "that don't care if init has happened or not")

(do-something-that-depenps-on-init @init)``````

didibus02:08:15

This assumes you'd be using threads, or that you don't care to have your single thread block once init is needed by some task. If you don't want to use threads, do the same thing but use a channel, and execute your tasks in GO blocks.

didibus02:08:39

``````(let [init (promise-chan)
start (promise-chan)]
(.onInit life-cycle #(>!! init %))
(.onStart life-cycle #(>!! start %)))

(go (do-something-that-depends-on-init (<! init)))``````

didibus02:08:22

This is literally why core.async was made 😛 to tame callback hell.

emccue03:08:43

I read it as .onInit is called by an outside system, its not a setter

emccue03:08:53

the "in callbacks in callbacks" part isn't super relevant I don't think in that situation

didibus03:08:47

I think its you provide a reified ILifeCycle which implements onInit and the framework will call the onInit. That's still fine for what I'm proposing, just you'd have to do:

``````(let [init (promise)]
(reify
ILifeCycle
(onInit [_this event]
(deliver init event))))``````
But ya, I'm making assumptions. If they don't even control the callback, then nothing can be done.

Jelle Licht11:08:56

This was all very useful folks, thanks! The thing with promise-chan seems to do the trick. (For context; the framework that actually invokes these callbacks has its own thread pool, and is A-okay with blocking calls, so my code will only be ugly, not ugly+broken 🙂 )

didibus17:08:00

FYI, putting in a promise-chan will never block. It's a dropping buffer of 1. The first put always succeeds, the following puts are dropped.

Jelle Licht23:08:00

Unless I am doing something Very Wrong, my IFoo will only be initialised and started once (each), but thank you for clarifying

sheluchin17:08:32

How would I write a spec for a vector of two ints, where the second element in the vector must be greater than the first? So `[1 2]` is valid but `[1 1]` is not.

alexmiller17:08:06

`(s/and (s/tuple int? int?) #(> %2 %1))`

sheluchin18:08:05

Simpler than I thought. Thanks!

sheluchin18:08:59

@U064X3EF3 Is it also possible with `s/coll-of`, or does the predicate there only act on individual items in the passed collection?

alexmiller18:08:54

Only individual elements

👍 3
sheluchin18:08:55

I think `#(> %2 %1)` needs to destructure %1, which is just the whole vector.

Cora (she/her)18:08:27

`#(apply < %)`

Cora (she/her)18:08:47

or make it a fn and destructure 🙂

👍 3
manas_marthi19:08:55

If I am building a banking app using clojure, can I apply uniform access controls to prod UI and prod repl ( 2 factor authentication, session time out)? Does the ability to repl and change code or run code under the guise of production support activity open a security hole and let me circumvent all change control processes?

drewverlee19:08:09

No more then if you could push that logic normally? And also inspect that running application. But I would defer to someone who has given this more thought.

manas_marthi19:08:46

Having access control and audit trail and being compliant of all regulatory requirements is my first concern.. In non repl apps, I have access controls upto linux terminal. And I got key stroke auditing in bash repl. But in case of clojure I need access controls upto jvm.. I haven't tried JMX much. It was more of SSL public key based auth

didibus02:08:56

It depends what kind of REPL you want. If you enable a local REPL only, where the REPL only allows connection from the machine itself. Then you can interact with it by ssh-ing into the machine, and if you have access control and ssh logging you'll get it for the REPL as well.

didibus02:08:22

If you use a remote REPL, then its a matter of your REPL, there might be a nREPL middleware for this, but I don't know of one that already exists which adds access control and logging.

Cora (she/her)19:08:16

> Does the ability to repl and change code or run code under the guise of production support activity open a security hole and let me circumvent all change control processes? I'm gonna say yes

Cora (she/her)19:08:04

but there are a lot of fintech companies running clojure, fwiw

drewverlee19:08:29

I mean, you can likely step up the repl connection such that it's auditable.

Cora (she/her)19:08:30

including nubank, which acquired cognitect, the company behind clojure

drewverlee19:08:59

Auditing isn't bashes responsibility either, I haven't done either so I can't speak to the gritty details.

manas_marthi19:08:51

We got different tools for key stroke logging. Not done by bash..

drewverlee19:08:07

Couldn't those be applied to a repl?

drewverlee19:08:29

Prob move this to a security channel

Cora (she/her)19:08:07

if I can change the code running in production then I can install all kinds of nasty things that go around normal channels. I'm interested how this is solved

drewverlee19:08:16

I don't disagree, I'm just not sure why that's unique to a repl. If I can update the app though any process I might be able to install nasty things. To be sure that it was less secure, I would have to know the existing security. Interesting topic, not sure I can chime in with anything else really.

Cora (she/her)20:08:58

the other processes generally

Cora (she/her)20:08:52

have other controls and checks on them

Cora (she/her)20:08:17

code reviews, change control

didibus02:08:26

The process can only run what its user has permissions to do. But the way this is handled is by securing the connection, the way that people allow ssh to prod machines for example, same thing

Cora (she/her)19:08:17

I just assumed they didn't allow repl in prod

Cora (she/her)19:08:29

a reverse shell is nasty business

manas_marthi19:08:32

Repl to live app is 50% of the USP of clojure?

practicalli-john20:08:41

A REPL is how Clojure code runs in any environment. It's a process that gives Clojure it's dynamic runtime on top of the JVM. A connection is required to access that REPL, either a terminal UI or a protocol such as nRepl or Socket Repl to connect an editor. When developing Clojure apps then I recommend a Repl connection 100% of the time. If 100% uptime was required, then a repl could be used to change a system without any downtime. I haven't had a need for a REPL connection to a Live production environment, as I would deploy new versions rather than change the running system. With effective load balancing then this gives a very high uptime of the overall service. If there is a business or technical need to live update a running system in production, then the system could be deployed with an active REPL connection. This should be secure, e.g. over an SSH connection with specific IP subnet or VPN connection. A REPL connection would give you access to change and add anything you wish. So with great power comes great responsibility... For an audit trail, a log capturing the messages over the REPL connection could be captured. For example all the nRepl messages.

👀 6
seancorfield20:08:43

And if you really want a "limited" REPL, take a look at projects like `clojail` -- but it's really hard to lockdown a REPL since you can call any code already in your program which could cause DB updates etc, unless you so severely limit the REPL to make those namespaces off-limits -- and then you might as well not bother with a REPL.

seancorfield20:08:21

If you deploy JAR files with AOT compiled code and direct linking enabled, that makes it much more difficult to apply live updates via a REPL (we've had to have that discussion at work: we decided fast startup for rolling upgrades in the cluster was more important than the ability to apply live patches via the REPL for most apps -- since our deployment pipeline is automated and we can roll new builds to production in about 15 minutes if we want to -- but we have one internal app that we run from source so we can apply live patches via a REPL without downtime, it isn't clustered).

manas_marthi21:08:48

Thank you John and Sean.

Cora (she/her)19:08:54

naaah, I rarely ever need that and I can do that at my job

Cora (she/her)19:08:05

I use the repl all the time for development though

Cora (she/her)19:08:58

I mean if that's 50% for you then 👍, everyone has different needs and wants 😊

manas_marthi19:08:23

That's a random number I threw

manas_marthi19:08:39

A read only repl login that can inspect data but is disabled for code changes with 2 factor authentication and key stroke logging, and session time out, is what is needed

didibus02:08:21

The read only part will probably not be possible. The best you can do is use SCI to setup a REPL within the SCI environment, and make sure you only expose to SCI APIs that are read only. But I wouldn't say that SCI has been pen tested or anything like that before, so who knows if there are some vulnerabilities to abuse in this scheme. But you can lock down access to the REPL by simply only allowing local connections through SSH, and securing your SSH connection.

indy06:08:48

Take a look at direct linking for some kind of “read-only”ness. Existing non-dynamic vars can’t be modified at runtime.

manas_marthi19:08:51

Or lockable namespaces? Like schemas in db

manas_marthi19:08:57

We disable db function /stored proc execution for prod support people in our prod Oracle dbs

practicalli-john20:08:41

A REPL is how Clojure code runs in any environment. It's a process that gives Clojure it's dynamic runtime on top of the JVM. A connection is required to access that REPL, either a terminal UI or a protocol such as nRepl or Socket Repl to connect an editor. When developing Clojure apps then I recommend a Repl connection 100% of the time. If 100% uptime was required, then a repl could be used to change a system without any downtime. I haven't had a need for a REPL connection to a Live production environment, as I would deploy new versions rather than change the running system. With effective load balancing then this gives a very high uptime of the overall service. If there is a business or technical need to live update a running system in production, then the system could be deployed with an active REPL connection. This should be secure, e.g. over an SSH connection with specific IP subnet or VPN connection. A REPL connection would give you access to change and add anything you wish. So with great power comes great responsibility... For an audit trail, a log capturing the messages over the REPL connection could be captured. For example all the nRepl messages.

👀 6