Fork me on GitHub
#beginners
<
2018-03-30
>
samcgardner13:03:42

What's the pattern to create a channel with both a buffer and a timeout? Create a channel with a buffer and then a transducer that reads it into a channel with a timeout?

Alex Miller (Clojure team)13:03:34

prob better to alt over the buffer channel and a timeout channel

samcgardner13:03:18

Yup, that looks sensible. Cheers!

danielo51514:03:56

Hello, after reading a good introduction to clojure async I am still some doubts

danielo51514:03:03

Hope someone can help me

danielo51514:03:21

First of all, and in the context of cljs, when should I use go and when should I don't use it ?

danielo51514:03:45

And I'm not clear about what's de difference betwen parked and not parked puts and gets in the context of cljs

bjr14:03:38

javascript doesn’t have threads, so the blocking (anything suffixed with !!) operations don’t exist in cljs core.async

danielo51514:03:10

Perfect, way easier 😃

danielo51514:03:27

Than what is the purpose of the go blocks ?

bjr14:03:19

>>> go is a macro that takes its body and examines it for any channel operations. It will turn the body into a state machine. Upon reaching any blocking operation, the state machine will be ‘parked’ and the actual thread of control will be released. This approach is similar to that used in C# async. When the blocking operation completes, the code will be resumed (on a thread-pool thread, or the sole thread in a JS VM). In this way the inversion of control that normally leaks into the program itself with event/callback systems is encapsulated by the mechanism, and you are left with straightforward sequential code. It will also provide the illusion of threads, and more important, separable sequential subsystems, to ClojureScript.

bjr14:03:13

It’s useful for more simply managing events normally handled by callbacks and for organizing code into separate concurrent subsystems, the “separable sequential subsystems”.

danielo51514:03:36

Is it of any use if what I need is cascade http requests ?

bjr14:03:36

Where requests are made consecutively, based on previous responses?

danielo51514:03:57

With some operations in between

bjr14:03:00

Probably, the code would look more linear

danielo51514:03:22

That one is for the browser I'm affraid

bjr14:03:30

Ah, doing this in Node?

danielo51514:03:46

So I'm wrapping request.js

bjr14:03:55

I haven’t used cljs with node but this comes up: https://github.com/quile/cljs-http-node

bjr14:03:05

Maybe not maintained

danielo51514:03:20

hahaha, yes, I saw both. I'll go with my own wrapper this time for learning purposes

danielo51514:03:14

But another doubt arises, about what is better practice: To return a closed channel from every async function (meant to be executed once), or to accept a channel and push the events to it ?

bjr14:03:05

I feel like I’ve seen that channel as an optional argument, but I can’t remember where.

danielo51514:03:40

Do you mean to accept a channel and create one if it does not exist ? And at the end, return that channel ?

danielo51514:03:03

But then you sholud not close it, because you don't know what people is going to do with it

bjr14:03:51

Right - some of the core.async fns which accept a channel to use for output have an optional close? argument for that reason

bjr14:03:08

like onto-chan and pipe

bjr14:03:47

I’m not sure what the best practice is - there are lots of options

danielo51514:03:20

hahaha, that seems to be a common answer for clojurians

danielo51514:03:28

Nothing seems right or wrong

bjr14:03:35

I usually just do what fits the immediate known use-case and either learn that’s fine or have to do a small refactor later. Right now I have a situation where I’m not sure how far to extend the boundary between core.async and the rest of my application — do I keep returning channels and offering blocking & parking variants of fns to support composition or do I hide it at some point?

danielo51514:03:11

Probably you will not know what is better until it bites you on the ass

danielo51514:03:20

I usually favor composition at all cost

bjr14:03:35

Yup - but fixing it won’t take long so the cost of making the mistake, learning & fixing the mistake is low

samcgardner14:03:10

I have some vector [1 2 3], how do I assoc with the map {} such that I get {:1 nil, :2 nil, :3 nil} ?

dpsutton14:03:27

Check out zipmap. Might be exactly what you want

dpsutton14:03:20

Which takes two sequences and zips them up into a map. So a sequence of nils might be useful. So look into repeat and repeatedly

samcgardner14:03:40

(repeat n nil)

dpsutton14:03:02

Without the n it should be an infinite list which might be safer for you

dpsutton14:03:43

Zipmap will stop at the shortest sequence so you would never want to omit your actual data because you ran out of place holder nils

samcgardner14:03:48

You can't call repeatedly on nil, it's a null pointer exception 😕

dpsutton14:03:19

Look at the doc differences in repeat and repeatedly

samcgardner14:03:14

Ah, (repeatedly #(nil))

schmee14:03:29

even better: (repeat nil)

schmee14:03:53

repeatedly is used when you need to call a function to produce your arguments

schmee14:03:59

for constants, prefer repeat 🙂

samcgardner14:03:04

Which, of course, you don't here

dpsutton14:03:18

And look at iterate which is always fun to use. It's not useful for you here but it's in that class of functions which make a sequence like here

danielo51516:03:30

One question

danielo51516:03:59

How can I pass different values to a function based on an if condition?

danielo51516:03:27

I mean, I have user and pass, then pass auth object + handler, if I don't have them just pass the handler

genmeblog16:03:30

(fn my-fn
 ([auth handler] (do-something...))
 ([handler] (do-something...)))

danielo51516:03:08

That is more or less what I'm trying

danielo51516:03:18

Should i not use defn ?

genmeblog16:03:50

yes, you should use defn

danielo51516:03:54

Then My automatic indentator is getting crazy about this

danielo51516:03:34

As you can see here it is understanding the let statement as part of the parameters list

(defn Get 
  ([url] (Get url nil nil))
  ([url usr pass
    (let [c (chan)

genmeblog16:03:41

it doesn't work like that

genmeblog16:03:27

you can't put let inside function parameters vector

schmee16:03:50

@rdanielo try having one space of indentation instead of two for the lists after Get

danielo51516:03:00

Yes, changing the indentation worked

danielo51516:03:22

This is my final version, which I don't like much. I would prefer to put the second let on a newline, but then my automatic paren tool will understand it as part of the parameters, or close the function at the same line

(defn Get 
  ([url] (Get url nil nil))
  ([url usr pass] (let [c (chan)]
                      (if (and usr pass) 
                          (req/get url (makeAuth usr pass) (handler c))
                          (req/get url (handler c))) 
                    c)))

bjr16:03:05

Really? what tool? parinfer should handle this

danielo51516:03:15

parinfer, on VSCode

danielo51516:03:23

(defn Get 
  ([url] (Get url nil nil))
  ([url usr pass] 
   (let [c (chan)]
      (if (and usr pass) 
          (req/get url (makeAuth usr pass) (handler c))
          (req/get url (handler c))) 
      c)))

danielo51516:03:37

The problem was the usage of tab for indenting, which by default inserts two spaces

facepalm 4
danielo51516:03:43

Seems that it was required to use one

ghadi16:03:08

Hola #beginners! Just wanted to share a small piece of Clojure code that uses a Java library, in order to demystify interop calls a bit. The goal of the code is to turn a UUID into a human readable mnemonic. The code takes: #uuid "59c5c57b-c0e1-4ab5-a941-814a66cfc8dc" and returns "flush comfort galaxy limb believe food pink coral engine cushion vendor rib" Then we can use test.check to run generative tests against the third party library, to make sure that all UUIDs can be read perfectly from their generated mnemonics.

🙌 4
ghadi16:03:44

the code is only < 30 lines

ghadi16:03:12

You can check whether it works manually:

;;; does it manually work?
(require '[clojure.string :as str])

(let [uuid (UUID/randomUUID)
      mnemonic (-> uuid uuid->bytes bytes->mnemonic)
      read-uuid (-> mnemonic mnemonic->bytes bytes->uuid)]
  {:original uuid
   :human-readable (str/join " " mnemonic)
   :roundtrip read-uuid
   :equivalent? (= read-uuid uuid)})

ghadi16:03:34

But that's boring! and it will never find edge cases

ghadi16:03:18

So we'll use test.check the generative testing library for Clojure to test it for us:

ghadi16:03:22

(require '[clojure.test.check.generators :as gen])
(require '[clojure.test.check :refer [quick-check]])
(require '[clojure.test.check.properties :refer [for-all]])

;; generator for random 16-byte arrays
(def gen-byte-array (gen/fmap byte-array (gen/vector gen/byte 16)))

;; property that asserts that a byte array can "roundtrip" through the mnemonic 
(def roundtrip (for-all [a gen-byte-array]
                (= (seq a)
                   (seq (-> a bytes->mnemonic mnemonic->bytes)))))

(quick-check 5000 roundtrip)

dpsutton16:03:11

Is this function 1-1?

dpsutton16:03:34

A collision of the mnemonic means the uuids were the same?

ghadi16:03:17

(Subtle point in the roundtrip property -- you have to call seq on the byte arrays because by default in Java byte arrays are compared for equality by reference or object identity, not by value like in clojure)

justinlee17:03:44

I hadn’t actually realized this. Should I think of identical? as reference equality and = as value equality for cljs collections? I guess I don’t often compare collections but it could be a surprising performance gotcha.

ghadi17:03:31

That is the correct mental model. It's not a performance gotcha as much as it is a correctness gotcha

ghadi17:03:55

= in Clojure delegates to .equals for some java classes. Which for byte arrays does reference equality

justinlee17:03:43

interesting thanks

justinlee17:03:06

i assume roughly the same thing in cljs? i don’t actually build stuff in clojure atm

ghadi16:03:33

Yes @dpsutton it is unique.

dpsutton16:03:41

Nice write-up

eternalNoob16:03:38

Hey #beginners , I have a small question. Does map always return a list if the result is linear in nature?

eternalNoob16:03:45

Example: (map inc [1 2 3]) returns a list

ghadi16:03:26

that's right. map is a sequence function. All sequence functions call seq on their input

ghadi16:03:47

to coerce their input into a seq (that's why you can pass a vector, and it works happily)

ghadi16:03:56

if you want a vector at the end, there is a function mapv, or you can call (into [] .....) at the end of a collection "pipeline"

ghadi16:03:16

or (into #{} ....) if you wanted a set

ghadi16:03:40

But don't use mapv everywhere -- if you have a bunch of operations in a pipeline (like map/filter/partition/etc.) you only need to pour into a [] at the last step

samcgardner17:03:26

Why is this a compilation error? It sure looks like recur is in the tail position to me

(defn build-sitemap
  [chan timeout-chan]
  (loop []
        (async/alt!!
             timeout-chan @sitemap
             chan ([url] ((client/get url {:async? true}
                           (fn [raise] nil)
                           (fn [response] (apply-response-to-state response url)))
                          (recur))))))

samcgardner17:03:33

(i.e. I'd like to handle messages to the channel by invoking a function with a callback on them and then invoking the function that listens to the channel again)

samcgardner17:03:31

<It isn't in the tail position, I wrongly double-bracketed the client/get stuff 😕 >

justinlee17:03:44

I hadn’t actually realized this. Should I think of identical? as reference equality and = as value equality for cljs collections? I guess I don’t often compare collections but it could be a surprising performance gotcha.

Russ Olsen17:03:41

@lee.justin.m Pretty much. Here's the doc string for identical? "Tests if 2 arguments are the same object"

Russ Olsen17:03:20

While = is the same as Java's equals method, which is more or less "is this value the same as this other value?"

Russ Olsen17:03:56

Let the philosophers argue over right and wrong, we programmers have our hands full with equality.

zaphodious19:03:35

In core.async, what is the best way to race two channels?

john19:03:37

I think you want alt! or alts!!

nando20:03:39

Coming back to Clojure after some time. I'm a perpetual newbie.

nando20:03:56

I'm having issues getting datomic-free working within a project.

nando20:03:01

When I try to eval the following:

(ns datomic-app.core
  (:require [datomic.api :as d]))

nando20:03:25

I get this error: FileNotFoundException Could not locate datomic/api__init.class or datomic/api.clj on classpath. clojure.lang.RT.load (RT.java:456)

nando20:03:11

It works for me within a lein repl, but not within a project file

seancorfield20:03:29

What do you mean "in a project file"? How are you running that file?

nando20:03:48

Atom Proto-REPL

seancorfield20:03:18

Sounds like your Atom REPL isn't using the same setup as your Leiningen project.

seancorfield20:03:45

Do you have multiple projects in Atom? The default is to start the REPL from the first project listed.

nando20:03:07

Ohhh - maybe that's it.

seancorfield20:03:23

You're on a Mac?

seancorfield20:03:38

And using Opt-Cmd-l to start your REPL?

nando20:03:23

On a mac, yes.

seancorfield20:03:25

If you open the project.clj for which you want a REPL (your Datomic API project), then Opt-Cmd-L (shift-l) will create a REPL from that project.clj file.

nando20:03:43

I'm just removing the projects above ...

seancorfield20:03:16

(or you can right-click > proto-repl > Start REPL from open project.clj/build.boot)

seancorfield20:03:37

There's a #protorepl channel if you need more extensive help with Atom/ProtoREPL.

nando20:03:43

Thanks Sean!

seancorfield20:03:54

Welcome back to Clojure BTW 🙂

nando20:03:09

Now I've got it working!

nando20:03:12

Thanks for the help, and the welcome.

nando20:03:48

^ I didn't realize it was a ProtoREPL issue ...

seancorfield20:03:00

Yeah, it catches everyone out at least once when they have multiple projects.

seancorfield20:03:01

I have multiple projects -- but I use Boot for everything and you can load new dependencies into the REPL easily with Boot so it's much easier to start a generic REPL and then just eval forms to add the dependencies you want at runtime.

danielo51521:03:00

How good is the experience with atom?

danielo51521:03:10

Clj or cljs?

dabrazhe21:03:53

Atom works fine with Clojure and boot for me. (besides being a big memory hog)

grierson21:03:38

@rdanielo Great! ProtoRepl, Parinfer, Joker linter, rainbow paren and you’re good to go. When Atom first came out it was unusable, now its good.

danielo51521:03:35

I'll give it a go, VSCode is not being a good experience

seancorfield21:03:50

@rdanielo I switched from Emacs to Atom/ProtoREPL about 18 months ago (I believe) after many years of Emacs. I really like it.

λustin f(n)23:03:49

Is anyone here familiar with Android development with re-natal (or other things)? I am trying to get started and would love any tips. Currently, I have the starter project loading on a device through USB, but am trying to get figwheel to connect.