Fork me on GitHub
#beginners
<
2019-12-29
>
Ian Fernandez04:12:40

I'm using some clojurescript and I don't know how to print a result from an #object[cljs.core.async.impl.channels.ManyToManyChannel]

Ian Fernandez04:12:30

can I do

(cljs.core.async/go 
  (println 
   (cljs.core.async/<! (fn))))

seancorfield04:12:07

@d.ian.b You'll need to give a bit more context. What is fn and what does (fn) return? And what values are being put into that channel?

seancorfield04:12:04

Assuming (require '[clojure.core.async :as a]),

user=> (def a (a/chan))
#'user/a
user=> (a/go (a/>! a 42))
#object[clojure.core.async.impl.channels.ManyToManyChannel 0x194a653d "clojure.core.async.impl.channels.ManyToManyChannel@194a653d"]
user=> (a/go (println (a/<! a)))
#object[clojure.core.async.impl.channels.ManyToManyChannel 0x23973547 "clojure.core.async.impl.channels.ManyToManyChannel@23973547"]
user=> 42

Ian Fernandez04:12:28

I'm using pathom and parallel-parser]

Ian Fernandez04:12:05

(def parser
  (p/parallel-parser
   {::p/env     {::p/reader [p/map-reader
                             pc/parallel-reader
                             pc/open-ident-reader]}
    ::p/mutate  pc/mutate-async
    ::p/plugins [(pc/connect-plugin {::pc/register app-registry})
                 p/error-handler-plugin
                 p/trace-plugin]}))

(cljs.core.async/go (println (cljs.core.async/<! (into [] (parser {} '[{::latest-product [:product/title :product/brand]}])))))

Ian Fernandez04:12:11

i'm doing something like this

Ian Fernandez04:12:04

(cljs.core.async/<! (parser {} '[{::latest-product [:product/title :product/brand]}])) => returns #object[cljs.core.async.impl.channels.ManyToManyChannel]

seancorfield04:12:28

I'm confused... in the first piece of code you have (into [] ,,,) and you're trying to call <! on ... a vector? but in the second piece of code you're calling <! on the result of the parser call, which doesn't seem the same...

seancorfield04:12:13

...and then if you're getting a channel back from <! then I can only deduce you're putting a channel onto a channel, rather than... the value you're looking for?

didibus07:12:15

Doing (println (<! chan)) inside a go block should do it

didibus07:12:35

It'll take the first element on the channel and print it

didibus07:12:58

The go function will return a #object[cljs.core.async.impl.channels.ManyToManyChannel]

didibus07:12:20

Are you sure you're not getting that confused with what println prints?

didibus07:12:28

They'll both show at the repl

didibus07:12:49

`=> (def c (a/chan)) #'c` `=> (a/go (println (a/<! c))) #object[cljs.core.async.impl.channels.ManyToManyChannel] => (a/put! c "hello") true hello`

didibus07:12:07

And because its async, the println will happen some time in the future, whenever parser is done doing what it does, and not as soon as you send the form to the repl

Ian Fernandez05:12:38

I've tried to put the result into a vector before print and returned me #object[cljs.core.async.impl.channels.ManyToManyChannel]

Ian Fernandez05:12:51

when I did (.log js/console (fn)) I could inspect the result,

Ian Fernandez05:12:53

maybe I'll stay here ^^'

Ian Fernandez05:12:12

I'll stay with "normal" parsers, until I understand how theses stuff works, thanks

hindol07:12:06

For CI/CD, which one is easier to setup? CircleCI or GitHub Actions? I am using Clojure CLI.

seancorfield20:12:56

They're both pretty easy but GitHub Actions means you don't need to do any setup so I'll say that's "easier". You can see what I've done for next.jdbc here: https://github.com/seancorfield/next-jdbc/blob/master/.github/workflows/test.yml

👍 4
hindol20:12:50

This example is fantastic. Thanks a ton!

David Pham12:12:03

In ClojureScript, is there a equivalent to jsoneditor to edit edn data structures? https://github.com/josdejong/jsoneditor

seancorfield20:12:56

They're both pretty easy but GitHub Actions means you don't need to do any setup so I'll say that's "easier". You can see what I've done for next.jdbc here: https://github.com/seancorfield/next-jdbc/blob/master/.github/workflows/test.yml

👍 4
adam.sz20:12:15

Hi, I’m coming from a Haskell background and I’m trying to learn Clojure. My first steps in pattern matching are pretty frustrating. My question is what is the most idiomatic way to write a code like this (minimal example):

fun :: [a] -> Int     -- type signature - function taking list of any a and returning a number
fun [] = 7            -- if the list is empty, just return 7
fun (x:xs) = fun xs   -- if the list is not empty (it has a head x), recursively call fun on its tail (possibly empty)
I came up with something like this:
(defn fun [u]
  (match [(first u) (rest u)]
    [nil _] 7
    [x xs] (fun xs)))
but I don’t like it, it seems verbose, I need to use first and rest explicitly and also I have no idea how to pattern match on an empty list (first pattern should be more like: [nil '()] but it generates compilation error.

manutter5120:12:41

In idiomatic Clojure, something like that is more commonly written like this:

(defn fun [[h & more]]  ;; <-- idiom: destructuring an array into a head and tail
  (if (seq more) ;; <-- idiom: seq returns nil if given an empty array (nil is "falsey")
    (fun more)
    7))

👍 4
andy.fingerhut20:12:50

if for two-way branching, or cond for multi-way branching is idiomatic. Pattern matching a la Haskell is not a part of Clojure, although there is a library or two that are not commonly used that provide something like it (although no static guarantees of exhaustivity of cases). Destructuring in Clojure is fairly commonly used: https://clojure.org/reference/special_forms#binding-forms

andy.fingerhut20:12:42

Also in that example (recur more) guarantees tail-call optimization with no call stack growth, which is important if the sequence length can exceed the call stack limit of the JVM (default is somewhere around 8000 somethings, but I forget the unit of the somethings)

☝️ 8
adam.sz21:12:20

Thanks a lot! I thought core.match is a way since it sits in core 😉

andy.fingerhut21:12:01

core.match is one of the libraries I was referring to (without names, sorry). It exists, and some fraction of people use it, but I don't believe it is widely used.

adam.sz21:12:18

Ok, so another example to make sure if I get it:

replaceFirstWith :: Eq a => a -> a -> [a] -> [a]
replaceFirstWith _ _ [] = []
replaceFirstWith y z (x : xs) | x == y = z : xs
replaceFirstWith y z (x : xs) = x : replaceFirstWith y z xs
In Clojure (first my initial version then after your comments):
(defn replaceFirstWith [y z u]
  (match [(first u) (rest u)]
    [nil _] '()
    [x :guard #(= % y) xs] (conj xs z)
    [x xs] (conj (replaceFirstWith y z xs) x)
  ))

(defn replaceFirstWith2 [y z [h & more]]
  (cond
    (= h nil) '()
    (= h y) (conj more z)
    :else (conj (replaceFirstWith2 y z more) h)
  ))
(let’s forget about tail recursion for a moment)

andy.fingerhut21:12:57

If you want the function to support nil as an element of a sequence, then the first condition would not be correct.

adam.sz21:12:44

So how to check if [h & more] is an empty list? In Haskell there is a pattern syntax p@(x:xs) (or using the same names whole_list@(h:more)) where p stands for whole matched list.

andy.fingerhut21:12:52

And I think perhaps the earlier example from @manutter51 did not handle the case of a single element sequence correctly

manutter5121:12:40

Hmm, should be ok for a single element list, but would probably bomb on an empty list, now that I look at it

manutter5121:12:10

The perils of playing "REPL in my head"

andy.fingerhut21:12:14

It is somewhat common in clojure.core code (which isn't always idiomatic) to define a separate function arity for the empty list, e.g. see (source max-key)

andy.fingerhut21:12:51

Here is one way to write the function fun above so that it handles empty sequences correctly:

(defn fun2 [[h & xs :as s]]
  (if (seq s)
    (+ h (fun2 xs))
    7))

andy.fingerhut21:12:19

Sorry, I added the (+ h ...) part myself, so I could test to see that it was doing something with every element

andy.fingerhut21:12:27

The :as s part of that seems to correspond fairly closely to the Haskell p@ syntax you describe above, but I'm not familiar enough with the Haskell version to know if they are identical in all ways.

adam.sz21:12:05

It seems that it does the same thing as in Haskell.

nooga21:12:44

yea, it just binds the whole value to a name

adam.sz21:12:10

So, I suppose the following modified definition should handle nil as an element of a sequence:

(defn replaceFirstWith2 [y z [h & more :as s]]
  (cond
    (= (seq s) nil) '()                    ;; ->   (empty? s) '()
    (= h y) (conj more z)
    :else (conj (replaceFirstWith2 y z more) h)
  ))
But in this case I have an impression that (= (seq s) nil) is a bit too much for such a simple check.

nooga21:12:59

nil?

👍 4
nooga21:12:53

you can also try (empty? s) it will return true for niland any kind of empty collection

adam.sz21:12:54

Thanks, I’m not yet familiar with the standard lib.

adam.sz21:12:12

empty? is actually implemented as (not (seq coll))) as I see

nooga21:12:17

one thing I like about Clojure is that it seems to handle nil very well, as in all std functions are made so that they don’t explode on nil

nooga21:12:30

so if you’re destructuring like this [a & as] and you don’t expect nils in your collection, you can just check if a is nil

nooga21:12:12

see: (let [[a & as] nil] [a as]) will return [nil nil]

adam.sz21:12:11

I see... It’s good to know that there are relatively sane conventions although I’m used to very strict static typing and I like when the code like this just does not compile. But I’m trying to convince myself to other benefits of this approach.

nooga21:12:53

yeah I get it, I had several attempts at Haskell and still can’t make it work for me 😂

nooga21:12:00

@adam.szlachta Clojure is amazing at data processing and transformation. I’ve rolled out two non trivial production systems since 2015

nooga21:12:09

written in Clojure

adam.sz22:12:51

I believe so! Clojure proficiency is my goal for 2020 😉

👍 4
parens 4
Drew Verlee16:12:21

oh no, you only have today!

andy.fingerhut22:12:42

It is considered more idiomatic in Clojure to use (seq s) rather than (not (empty? s)) but it certainly may be less obvious to someone new to Clojure. If you want to avoid a not call, you can put the non-empty case first, before the empty case.

adam.sz22:12:33

But in most cases (and what I got used to) firstly I check for an empty list (corner case), and (empty? s) does the job, no need for not. So remaining conditions will be implicitly (not (empty? s)) which does not have to be checked anymore.

andy.fingerhut22:12:10

nil is logical false in if/cond/when conditions, as well as false , and this is relied on quite often, so it is quite rare to see (nil? ..) in a conditional expression.

👍 4
naxels22:12:05

Besides Parens of the Dead, what videos would someone recommend of a person solving problems using Clojure? (I am also listening to the Functional Design in Clojure podcast, but have a hard time following the later episodes)

naxels22:12:52

I also bought Grokking Simplicity from Eric Normand, which helps with the functional mindset