Fork me on GitHub
#beginners
<
2017-06-03
>
john00:06:25

@noisesmith and I just posted a few versions of this very answer not even a few days ago. The log got eaten though.

vitruvia00:06:59

really? I wonder if someone is doing the same exercises as me

john00:06:13

Hint: you can do it with the rests function. or the heads function, in my last example above.

vitruvia00:06:43

I can't find the docs for these functions

john00:06:07

Here are the three examples I ended up with:

;; descriptive, transducers
(defn longest-increasing [number-sequence]
  "Finds the longest subsequence of consecutively increasing numbers."
  (let [longest #(apply max-key count %) 
        increasing? #(every? #{-1} (map (partial apply -) (partition 2 1 %))) 
        long-enough #(if (< 1 (count %)) (vec %) []) 
        rests #(take (count %) (iterate rest %))
        exploder (comp (map reverse) (mapcat rests) (map reverse)) 
        increasers (comp exploder (filter increasing?) (map long-enough)) 
        increasing #(sequence increasers (rests %))] 
    (-> number-sequence (increasing) (longest))))

;; descriptive, thrush
(defn longest-increasing [number-sequence]
  (let [longest #(apply max-key count %) 
        increasing? #(every? #{-1} (map (partial apply -) (partition 2 1 %))) 
        long-enough #(if (< 1 (count %)) (vec %) []) 
        rests #(take (count %) (iterate rest %))
        exploder #(->> % (map reverse) (mapcat rests) (map reverse)) 
        increasers #(->> % (exploder) (filter increasing?) (map long-enough)) 
        increasing #(-> (rests %) increasers)] 
    (-> number-sequence (increasing) (longest))))

;; terse, thrush
(defn longest-increasing [x] 
  (->> (take (count x) (iterate rest x)) 
       (map reverse) 
       (mapcat #(take (count %) (iterate rest %))) 
       (map reverse) 
       (filter #(every? #{-1} (map (partial apply -) (partition 2 1 %)))) 
       (map #(if (< 1 (count %)) (vec %) [])) 
       (apply max-key count)))

vitruvia00:06:09

I'll read up on some of these functions

john00:06:38

It uses a similar method as the last problem of rotation you brought up.

john00:06:55

(iterate rest x)

vitruvia00:06:51

Yes, I'm beginnig to think I get it xD

john00:06:53

In this version, we use rests twice, once forwards, once backwards, to explode all combinations of the data

john00:06:27

if you were allowed to bring in combinatorics it'd be cleaner

vitruvia00:06:31

I think I'm supposed to use functions the most basic possible

john00:06:57

right, so either thrush version above will work

john00:06:13

transducers unfortunately don't work in 4clojure.

john00:06:43

But you should come up with your own 😉

vitruvia00:06:59

yup I will try to

vitruvia00:06:10

but thanks for the examples

john00:06:15

One improvement would be to make it so it doesn't generate the extra garbage structures that it eventually throws away, with the whole exploder process.

victora03:06:27

user=> (def edges "0 4\n0 2\n0 5\n1 0\n2 1\n2 5\n3 6\n3 1\n4 0\n4 5\n6 3\n")
#'user/edges
user=> edges
"0 4\n0 2\n0 5\n1 0\n2 1\n2 5\n3 6\n3 1\n4 0\n4 5\n6 3\n"
user=> (clojure.string/split-lines edges)
["0 4" "0 2" "0 5" "1 0" "2 1" "2 5" "3 6" "3 1" "4 0" "4 5" "6 3"]
user=> (map clojure.string/split (clojure.string/split-lines edges) #" ")
IllegalArgumentException Don't know how to create ISeq from: java.util.regex.Pattern  clojure.lang.RT.seqFrom (RT.java:542)

victora03:06:36

Can someone help me with this error?

noisesmith03:06:47

every arg to map after the first needs to be a collection

noisesmith03:06:52

if your function needs another arg, after the thing you map over, you need to make a lambda for that

victora03:06:11

Yeah, it worked with lambda.

victora03:06:23

I'm not exactly sure why I thought it would work otherwise, but yeah, thanks!

victora03:06:18

How would you go about transforming this:

(["0" "4"] ["0" "2"] ["0" "5"] ["1" "0"] ["2" "1"] ["2" "5"] ["3" "6"] ["3" "1"] ["4" "0"] ["4" "5"] ["6" "3"])
Into something like
{:0 [4 2 5], :1 [0], :2 [1 5], ...}
?

eggsyntax13:06:35

victora: Much belated, but happened to see this & thought it would be a fun little problem. It’s not very different from @noisesmith ‘s solution, but I would be inclined to do it like this:

(reduce (fn [m [k v]] (assoc m k (conj (m k) v)))
        {}
        '(["0" "4"] ["0" "2"] ["0" "5"] ["1" "0"] ["2" "1"] ["2" "5"] ["3" "6"] ["3" "1"] ["4" "0"] ["4" "5"] ["6" "3"]))

eggsyntax13:06:35

(and then if you want to convert them to ints, before or after the above, you can do

(clojure.walk/prewalk (fn [x] (if (string? x) (java.lang.Integer/parseInt x) x)) a)
)

eggsyntax13:06:47

That does get them backward, but I’m assuming you just want a collection of them. I can add a step to switch them if you like.

noisesmith14:06:32

1) you never need to refer to java.lang explicitly it's always in scope 2) prewalk is heavy handed when you already know the precise shape of the collection 3) (assoc m k (conj (m k) v)) should be written as (update m k conj v)

eggsyntax02:06:23

1) excellent point, it’s a bad habit of mine. 2) I disagree; prewalk or postwalk conveys the intention very clearly: you want to preserve the structure of this data, but perform some operation on each node. 3) Good point, thanks, although I can go either way on that one.

noisesmith03:06:25

the reader accepts number keywords but they are technically illegal

noisesmith03:06:28

you can just use numbers

noisesmith03:06:32

anyway, zipmap

noisesmith03:06:29

oh, you also want to split that series in specific places and parse the numbers...

john03:06:04

some kinda conj/map-keys/juxt thing

noisesmith03:06:20

so first you need a reduce to separate the keys and values

noisesmith03:06:24

then apply hash-map

noisesmith03:06:32

or you could combine them

noisesmith03:06:44

oh, and apply concat to flatten it out a bit

victora03:06:58

Yeah, python equivalent of what I'm trying to do

m = {}
for p in pair_list:
    if p[0] not in m:
        m[p[0]] = [p[1]]
    else:
        m[p[0]].append(p[1])
return m

noisesmith03:06:21

yeah - that doesn't use keywords or parse the strings, but OK

victora03:06:36

that's right, i forgot parsing

victora03:06:58

let me look up those things you said

noisesmith03:06:22

+user=> (->> [["0" "4"] ["0" "2"] ["0" "5"] ["1" "0"] ["2" "1"] ["2" "5"] ["3" "6"] ["3" "1"] ["4" "0"] ["4" "5"] ["6" "3"]]
            (reduce (fn [m [k v]] (update m k (fnil conj []) v)) {}))
{"0" ["4" "2" "5"], "1" ["0"], "2" ["1" "5"], "3" ["6" "1"], "4" ["0" "5"], "6" ["3"]}

noisesmith03:06:28

I didn't understand the task at first

noisesmith03:06:41

that's the best way to do it - I understand now that I see your python version

noisesmith03:06:07

(the code is almost the same if you are parsing too, it should be clear where the parsing goes)

noisesmith03:06:33

->> is only in there to make the example a little easier to read, the input on one line, the code processing it on the next, it's not doing anything really

victora03:06:34

Let me digest that a bit.

noisesmith03:06:54

the one part that you can't just google / ask for doc for in the repl is the destructure, but if you google for clojure destructuring you should find something good on http://clojure.org

victora03:06:16

Surprisingly destructuring is the only thing i fully understand there .-.

noisesmith03:06:52

update accepts a map, a key, a function, and n args, changing the value under the key using the function and args

noisesmith03:06:00

with the existing value as the first arg to said function

noisesmith03:06:29

fnil accepts a function and a default value, and returns a function which when called with nil as its first arg, replaces it with the value

noisesmith03:06:37

otherwise acting like the original function

noisesmith03:06:05

fnil is mostly used with 0 (when you are using a map to accumulate a quantity) or an empty collection (when using a map to accumulate some collection type but don't want lists)

john03:06:29

Pretty much what I came up with:

(->> 
  [["0" "4"] ["0" "2"] ["0" "5"] ["1" "0"] ["2" "1"] ["2" "5"] ["3" "6"] ["3" "1"] ["4" "0"] ["4" "5"] ["6" "3"]] 
  (reduce #(update %1 (first %2) conj (second %2)) {}))

noisesmith03:06:47

but that returns backward lists

noisesmith03:06:07

which is why I used fnil to get vectors in the right order

victora03:06:25

reduce is the thing that's bothering me now. I know what it does on paper but people use it to 'cheat' loops

john03:06:27

and it returns lists

noisesmith03:06:39

@victora it's not a cheat, reduce is a looping structure

john03:06:03

it's idiomatic for reducing loops

noisesmith03:06:07

with the stipulation that you loop over elements of a collection

noisesmith03:06:42

it even has early return

noisesmith03:06:08

also, when your loop is accessing items of a collection, it's better to use reduce if possible, because it's faster

noisesmith03:06:14

(as opposed to loop)

noisesmith03:06:19

@victora another thing that helps a lot is reductions - it's like reduce but it returns a lazy seq of each intermediate state of the accumulator

victora03:06:40

your reduce's function takes two arguments - a map and a "pair" - and updates the map with this pair, correct?

victora03:06:47

how does it iterate through my list of lists?

noisesmith03:06:57

because that's what reduce does, it iterates over lists

victora03:06:01

I thought it would take elements two-by-two and apply a function over them

victora03:06:14

like in

(reduce + [1 2 3 4 5]) 

noisesmith03:06:31

user=> (->> [["0" "4"] ["0" "2"] ["0" "5"] ["1" "0"] ["2" "1"] ["2" "5"] ["3" "6"] ["3" "1"] ["4" "0"] ["4" "5"] ["6" "3"]]
            (reductions (fn [m [k v]] (update m k (fnil conj []) v)) {})
            (pprint))
({}
 {"0" ["4"]}
 {"0" ["4" "2"]}
 {"0" ["4" "2" "5"]}
 {"0" ["4" "2" "5"], "1" ["0"]}
 {"0" ["4" "2" "5"], "1" ["0"], "2" ["1"]}
 {"0" ["4" "2" "5"], "1" ["0"], "2" ["1" "5"]}
 {"0" ["4" "2" "5"], "1" ["0"], "2" ["1" "5"], "3" ["6"]}
 {"0" ["4" "2" "5"], "1" ["0"], "2" ["1" "5"], "3" ["6" "1"]}
 {"0" ["4" "2" "5"],
  "1" ["0"],
  "2" ["1" "5"],
  "3" ["6" "1"],
  "4" ["0"]}
 {"0" ["4" "2" "5"],
  "1" ["0"],
  "2" ["1" "5"],
  "3" ["6" "1"],
  "4" ["0" "5"]}
 {"0" ["4" "2" "5"],
  "1" ["0"],
  "2" ["1" "5"],
  "3" ["6" "1"],
  "4" ["0" "5"],
  "6" ["3"]})
nil

noisesmith03:06:38

see, reductions shows each step

noisesmith03:06:39

it doesn't take elements two by two, it takes them one by one, and combines them with an accumulator

noisesmith03:06:50

if you don't provide an accumulator, the first element of your collection is used implicitly

victora03:06:58

Okay, now I understand the difference. Reduce behaves differently depending on the number of arguments passed

victora03:06:06

(reduce f coll) vs (reduce f val coll)

noisesmith03:06:14

only in terms of the accumulator and the first element of the coll

noisesmith03:06:22

the behavior is only trivially different

victora03:06:30

is read-string the right way to parse string to int?

noisesmith03:06:38

avoid read-string

noisesmith03:06:48

on the jvm, use Long/parseLong

noisesmith03:06:22

read-string allows arbitrary code execution, and it's a bit overpowered when you know all you want is to parse a number of a known type

john03:06:06

cljs: (js/Number "1")

victora03:06:26

user=> (Long/parseLong "3")
3
user=> (map #(map Long/parseLong %) [["0" "1"] ["0" "2"]])
CompilerException java.lang.RuntimeException: Unable to find static field: parseLong in class java.lang.Long, compiling:(NO_SOURCE_PATH:5:7) 

noisesmith03:06:54

methods are not first class

noisesmith04:06:06

#(Long/parseLong %)

victora04:06:08

That makes sense

victora04:06:28

but I do need a second map, cause I need to apply parseLong within the nested list

noisesmith04:06:29

functions are actually objects with an invoke method on it 😄

noisesmith04:06:29

you can use fn, since you can't nest #()

noisesmith04:06:55

or, you can just use (fn [m [k v]] (update m (Long/parseLong k) (fnil conj []) (Long/parseLong v)))

noisesmith04:06:11

since you know the precise structure and it's small

victora04:06:27

using fn to transform a method to first class is standard procedure or considered dirty?

noisesmith04:06:48

it's the only way to do it

noisesmith04:06:03

oh, there is that

noisesmith04:06:26

@john if you macroexpand, it creates an fn 😄

john04:06:49

it's all about not polluting that anonymous syntax 😉

john04:06:05

memfn, partial

noisesmith04:06:05

+user=> (macroexpand-1 '(memfn length))
(clojure.core/fn [target58] (. target58 (length)))

victora04:06:39

user=> map #(map (fn [x] (Long/parseLong x)) %) [["0" "1"]]
#object[clojure.core$map 0x16c069df "clojure.core$map@16c069df"]
#object[user$eval45$fn__46 0x2a7ed1f "user$eval45$fn__46@2a7ed1f"]
[["0" "1"]]
where did I go wrong?

victora04:06:27

I should probably set up an IDE or smth, right?

noisesmith04:06:02

that's a matter of preference, the repl can do a lot on its own

noisesmith04:06:19

I rely on load-file, clojure.repl/doc clojure.repl/source, the :reload optional arg to require

noisesmith04:06:30

and I don't use editor/repl integration

victora04:06:56

split-lines and split return vectors, but map return lists, right?

noisesmith04:06:59

but if you like real IDEs cursive is great

noisesmith04:06:07

LazySeqs, yeah

victora04:06:30

is the repl what I get by simply using the command "clojure"? Cause that thing is awful lol

noisesmith04:06:31

but unless you are looking up by index, you probably don't need to care

victora04:06:54

I don't even have history

noisesmith04:06:56

you can also use leinengen (a project manager) that gives you a repl with your project deps

noisesmith04:06:15

@victora you can use rlwrap for that (that's what I use when not working on a project)

john04:06:07

You get used to it.

victora04:06:57

Ok, next is an important question. How would you indent this piece of code?

(defn graph [e]
   (reduce (fn [m [k v]] (update m k (fnil conj []) v)) {} (map #(map (fn [x] (Long/parseLong x)) %) (map #(clojure.string/split % #" ") (clojure.string/split-lines edges)))))

noisesmith04:06:39

(defn graph [e]                                                                 
   (reduce (fn [m [k v]]                                                        
             (update m k (fnil conj []) v))                                     
           {}                                                                   
           (map #(map (fn [x]                                                   
                        (Long/parseLong x))                                     
                      %)                                                        
                (map #(clojure.string/split % #" ")                             
                     (clojure.string/split-lines edges))))) 

noisesmith04:06:50

I would never nest that many maps btw

victora04:06:48

It certainly can be done with one

noisesmith04:06:10

(defn graph [e]                                                                 
  (reduce (fn [m [k v]]                                                         
            (update m k (fnil conj []) v))                                      
          {}                                                                    
          (map (comp #(map (fn [x]                                              
                             (Long/parseLong x))                                
                           %)                                                   
                     #(clojure.string/split % #" "))                            
               (clojure.string/split-lines edges)))) 

noisesmith04:06:55

also there's a function called transduce, for when you are doing a reduce but need to do a stream transform on the input data

john04:06:22

parinfer rocks too

victora04:06:22

what is comp?

noisesmith04:06:47

(defn graph [e]                                                                 
  (transduce (map (comp #(map (fn [x]                                           
                                (Long/parseLong x))                             
                              %)                                                
                        #(clojure.string/split % #" ")))                        
             (fn [m [k v]]                                                      
               (update m k (fnil conj []) v))                                   
             {}                                                                 
             (clojure.string/split-lines edges)))  

noisesmith04:06:08

@victora (comp f g) is like (fn [x] (f (g x)))

noisesmith04:06:55

like the dot operator in algebra (composition)

john04:06:20

It actually easy to translate comped transducers as a ->> thrush, if that helps you think about it

noisesmith04:06:39

right, but there's no comped transducers here

noisesmith04:06:50

there are comped functions in a map though

john04:06:03

ah, I see

john04:06:19

which doesn't look as much like a ->> thrush

victora04:06:11

map is a hash map, right? not a tree. And update is expected O(1)

noisesmith04:06:46

which map are you talking about? {} is a persistent hash map, it has Log(n) update

noisesmith04:06:01

but it's actually Log32(n) or something

victora04:06:41

Can I make it not persistent if I need the performance? Or should I just use a java lib then?

noisesmith04:06:14

it's close enough to constant time that I've only had it as a bottleneck once, and that was mostly because of the whole immutability thing making so much garbage

noisesmith04:06:45

we can use java hash maps, measure before doing that optimization, in years of usage I've only had to do that once

noisesmith04:06:05

and that was because I was doing a graph analysis that took nearly half an hour

noisesmith04:06:29

(using an algorithm that did lots of hash-map updates of course)

victora04:06:30

Log32 should be basically O(1) in most situations

victora04:06:31

If I'm interpreting a file, can I make it print the things that the REPL does?

noisesmith04:06:07

sure - clojure doesn't technically interpret, the only way to run clojure is via the compiler

noisesmith04:06:13

as far as that distinction matters

victora04:06:29

right, it runs on the JVM

noisesmith04:06:51

but yeah, it would be a question of getting a PushbackReader from the file, and doing a loop of reading a form and printing the output of evaluating the form

noisesmith04:06:14

right -but not only that, it doesn't have an interpreter, the only way to run clojure code is to create bytecode and run it

noisesmith04:06:20

(on the jvm at least)

noisesmith04:06:37

most of the time that distinction doesn't matter, but just being clear

victora04:06:39

which is weird considering how easy it is to write interpreters for functional languages

noisesmith04:06:50

but clojure avoids special cases

noisesmith04:06:23

for example, when you load a file, the same exact code paths are run as if you typed the same thing into the repl (with very minor differences, a few booleans that get set for class file generation on disk)

noisesmith04:06:01

which can surprise people - they expect to be able to use a definition higher in the file then they specify it for example

noisesmith04:06:18

but clojure is following the same rules as the repl - if you haven't run it yet, the definition doesn't exist and it errors

noisesmith04:06:58

but it avoids having to remember all kinds of interpret vs. compile special cases (which in many lisps turn into two different languages over time)

victora04:06:29

That's cool I guess

victora04:06:12

But why not only interpreter?

noisesmith04:06:37

because clojure is intended to be relatively efficient

noisesmith04:06:15

there's lots of things that could be a little more normal, or friendly, but clojure opted for the thing that performs well without introducing complexity (if possible)

victora04:06:27

{0 [4 2 5], 1 [0], 2 [1 5], 3 [6 1], 4 [0 5], 6 [3]}
This is the map created by our code. Why isn't it shown with the ':' before keys?

john04:06:08

The first character in a keyword should not be a number

john04:06:31

But it's because you didn't make it one

john04:06:42

(keyword "1")

noisesmith04:06:57

@victora keyword is a not a special "this is a key in a map" data type

noisesmith04:06:05

it's a special variation on a symbol

noisesmith04:06:26

in json the : is not part of the data, it indicates a key/value pair

noisesmith04:06:51

in clojure :foo is a specific data type, a keyword, it can exist outside hash maps, and all types are allowed as hash map keys

victora04:06:16

That's wonderful news actually

noisesmith04:06:51

keywords are especially useful as hash-map keys because when used as functions they attempt to look themselves up in their arg

noisesmith04:06:07

but they are not needed - if your data is numeric, just use numbers 😄

noisesmith04:06:01

symbols also look themselves up as args, but they are mainly used in constructing macros or looking up namespaces or values in namespaces (which is actually what they do in macros...)

john04:06:42

And because keywords are constructed with symbols, symbols cannot start with a number, therefore some things throw on :1

victora04:06:57

Okay, what I'm trying to do may be very dumb and unorthodox, but I'm trying to code algorithms I'm familiar with in clojure, to learn you know. How do I deal with needing a fixed-size mutable array in clojure? e.g. bool visited[n]

victora04:06:14

Without losing efficiency, ofc. Is that even possible?

noisesmith04:06:28

if it needs to be mutable you might want to learn an immutable alternative as part of learning clojure

noisesmith04:06:48

you can use mutable types, the java arrays for example, but this is abnormal and only done in extreme cases

john04:06:34

There's transient

victora04:06:36

Btw, I need to learn clojure till next friday

noisesmith04:06:44

in general, until profiling shows you that your data updates are a bottleneck, use the default clojure collections and replace mutation with recursion that sets a new binding

noisesmith04:06:04

@john using transient as if it actually mutated the collection is buggy

noisesmith04:06:14

it's not reliable, when it works it works accidentally

john04:06:34

Yeah, you should keep it functional. But it can speed up hot zones.

noisesmith04:06:36

the correct way to use a transient is to always bind the return value, just as you would with an immutable object

noisesmith04:06:45

right - by about 15%

noisesmith04:06:06

use things that use transients implicitly (into) because why not, but the small speedup is rarely worth the 15% speedup

noisesmith04:06:10

(when using them directly)

victora04:06:20

I'm thinking about the DFS code, ok? I need to mark a node visited whenever he is. I could do that with a map, and the lookup/update time shouldn't take much longer than O(1). It seems like overkill though

victora04:06:28

Is there any (more usual) other way?

noisesmith04:06:52

the usual thing is to use immutable data

victora04:06:34

But what's your suggestion?

john04:06:02

What is the DFS code?

noisesmith04:06:17

use immutable data, if profiling shows the immutable updates are the bottleneck, use mutable data

noisesmith04:06:31

if you do it right the translation is trivial

victora04:06:06

def dfs(v):
    visited[v] = true;
    for w in neighbors[v]:
        if not visited[w]:
            dfs(w)

victora04:06:45

So what's mutating is the boolean vector visited

noisesmith04:06:31

right, we would do this with a recursive function call that takes visited as an arg

noisesmith04:06:27

there's a proof out there that every mutable algorithm can be replaced by an immutable one where every accessed mutable data item is replaced with an extra argument to each function that uses it (and re-bound based on the return value)

victora04:06:03

Vector being immutable means everytime I change something I'm actually creating a new one, like strings in java?

noisesmith04:06:13

no, they are persistent

noisesmith04:06:18

which means they share structure

john04:06:25

Not a full copy

noisesmith04:06:33

since you know that nobody else can mutate the vector, you can reuse the parts you don't update

victora04:06:40

It's just one is 'ahead in time'?

akiroz04:06:45

my random 2c: for standard stuff like DFS there's clojure.walk/prewalk

noisesmith04:06:07

oh, that's a good point yes (or clojure.walk/walk that can combine pre and post walk actions)

noisesmith04:06:24

or tree-seq if you just want to hit each node of a tree

victora04:06:55

@akiroz Is the code somewhere?

victora04:06:24

Like, how it is implemented

noisesmith04:06:33

@victora tree-seq and clojure.walk come with clojure and you can see source with the source function in the repl also

noisesmith04:06:06

on top of that, clojure.jar can be unzipped and you can read the clj files inside

noisesmith04:06:14

or just browse github of course

victora04:06:36

Nice, didn't know those were the ways

akiroz04:06:52

+1 on (source <fn>) 🙂

victora04:06:21

functions have a 'do' structure, right?

john04:06:57

implicit do. They return the return val of the last form.

noisesmith04:06:13

@victora I bet what you are actually looking at is a multi-arity function

noisesmith04:06:16

just a guess

noisesmith04:06:01

yes, function bodies always have an implicit do

noisesmith04:06:19

but clojure.core doesn't use that, which is why I thought you were mistaking a multi-arity function

noisesmith04:06:27

well - rarely I guess

victora04:06:20

If I'm calling 3 times dfs from inside one instance, I need to combine the 3 returns right?

victora04:06:43

Let me elaborat

victora05:06:34

In one call to dfs I may call dfs multiple times. All of them are "contributing" and changing the visited array in my python implementation.

victora05:06:58

But I can't just return the last, I need to combine them

akiroz05:06:25

actually, what are you trying to do by performing a DFS?

victora05:06:54

For the sake of learning the language, I'm starting by just marking the reachable nodes

noisesmith05:06:56

dfs

(some #(= % :a) (tree-seq coll? seq [[:b :c] [:d :e :f [:g :a] :a]]))
true

noisesmith05:06:16

if you need to accumulate state, use reduce over the tree-seq

noisesmith05:06:18

or you can use a zipper for more elaborate traversal - clojure.zip

noisesmith05:06:40

but this isn't the easy way to get into clojure - some things that are very tricky in other langs are simple here, and some things easy in others are more complex in clojure

john05:06:58

lots of ways to skin that cat

noisesmith05:06:07

unless you have a pressing reason, I don't think starting with tree walking and marking nodes will be rewarding at all

noisesmith05:06:16

as an intro to the language at least

noisesmith05:06:40

I mean, we have some nice tricks but they will look super weird

john05:06:32

And if you're using DFS just to walk some data, lots of clojure functions walk data

noisesmith05:06:55

right, we have easier ways to do this stuff but it won't look like a dfs

victora05:06:19

I'll probably be asked to solve programming contest problems

akiroz05:06:26

yeah, reducing over a tree-seq usually takes the place of performing DFS in clojure.

victora05:06:36

graphs are a recurrent topic

noisesmith05:06:40

I mean - it is actually a dfs 😄

noisesmith05:06:05

@victora yes they are, I use graphs as my primary domain object, in clojure I always turn them into an adjacency list

noisesmith05:06:18

(my day job is messing with graphs in clojure)

akiroz05:06:23

Ahh right, DFS but not as we know it 😛

victora05:06:59

Do we have an equivalent for

visited = new bool[n] 
?

noisesmith05:06:29

yes but I'd never do it that way in clojure except as a last resort

noisesmith05:06:56

(boolean-array n)

noisesmith05:06:01

I've never used that

noisesmith05:06:04

but it exists

victora05:06:21

Which structure would you rather use?

akiroz05:06:45

Wow, I've never even heard of that... does it just wraps over a java boolean array?

noisesmith05:06:52

I would map a hash-map from node id to a hash of properties, including visited

noisesmith05:06:03

@akiroz right, that's exactly what it is

victora05:06:27

Ok, so in clojure we like maps haha

noisesmith05:06:36

in some cases I might make the properties part of my adjacency list, sometimes a separate map with the same keys

noisesmith05:06:05

@victora yeah, they are the easy way to do things in clojure, especially if you need to do lookups

john05:06:34

keys are functions of maps and maps are functions of keys

victora05:06:37

Any reason we don't close ) in the same indentation level as ( ?

noisesmith05:06:55

because hanging ) are noise

akiroz05:06:04

There's also metadata... a bit fiddly since they only work on collections but works

noisesmith05:06:08

it's a lisp tradition going back to the '50s

noisesmith05:06:19

yeah, I'd never rely on metadata for this sort of thing

noisesmith05:06:42

for example I can't put metadata on arbitrary data that I use as graph identifiers

akiroz05:06:46

I used it once for a mini DSL

noisesmith05:06:51

but I can make a hash-map from identifier to properties

noisesmith05:06:27

@akiroz sure, but this use case of marking a node visitor, most of the good graph node id types don't accept metadata

noisesmith05:06:08

@victora thinking a moment longer, I would actually use a hash-set of node-ids when keeping track of what I have visited

noisesmith05:06:44

like, that's literally what I pass along in my traversals for cyclical data

noisesmith05:06:53

for non-cyclical, I stick with simpler things though

victora05:06:07

hash-set means you can check if it's in there as fast as you could lookup in a map, right?

victora05:06:12

set in C++

noisesmith05:06:13

right, exactly

noisesmith05:06:31

a hash-set also acts as a function that returns its arg if the arg is present in it

noisesmith05:06:34

otherwise nil

noisesmith05:06:32

so really, they are what we would use instead of indexed boolean arrays for checking membership/non-membership

noisesmith05:06:43

unless doing crazy optimizations

victora05:06:46

do we have stack queue priority_queue in the core?

noisesmith05:06:07

we have clojure.lang.PersistentQueue

noisesmith05:06:14

it's a little weird to use, but it's good

noisesmith05:06:00

+user=> (-> clojure.lang.PersistentQueue/EMPTY (conj 1 2 3) (pop) (conj 44 55) (pop) (seq))
(3 44 55)

noisesmith05:06:25

just be aware that the only operations that preserve the type of the queue are peek, pop, conj, and others built on those (like into)

noisesmith05:06:47

I had a bug in my app where someone called rest on a queue, returns a list, and turned a fifo into a lifo

noisesmith05:06:12

and it was subtle because lists also implement stack so peek and pop work on them

noisesmith05:06:36

well - peek doesn't preserve type (it returns one element) but it's meant to be used with the stacky types like queue and list

victora05:06:10

If I want to use (for Object o : list) then I'm looking for reduce, right?

noisesmith05:06:13

unless only working on side effects and you don't want to short circuit - in that case doseq also works

noisesmith05:06:23

but if building a result functionally, reduce is usually what you want

noisesmith05:06:03

there's also for which is a list comprehension, which is the right thing for lazily generating a series from N input series

noisesmith05:06:09

but that's probably not what you want here

victora05:06:56

do we have list comprehension like in python?

noisesmith05:06:12

that's what for is - I don't think it maps 1:1 but it does most of the stuff

victora05:06:28

That's nice, some ellegant stuff can be done with it

noisesmith05:06:16

it also accepts :let and :while keys

victora05:06:06

If what my function only does is one reduce, what does it return?

victora05:06:10

The value after the last iteration, right?

noisesmith05:06:52

right - often my reduce is surrounded by another form that grabs what I really want out of the accumulator

noisesmith05:06:23

with transduce, the reducing function can accept a one-arg arity that is used as the "finalize" on the accumulator value

victora05:06:59

Exception in thread "main" java.lang.ClassCastException: java.lang.Boolean cannot be cast to clojure.lang.IFn

victora05:06:22

I found one mistake and it did not fix what I needed

noisesmith05:06:24

you can use not as the update function

noisesmith05:06:46

because (update x y true) tells it to use true as a function, which is invalid

noisesmith05:06:54

but not works, and returns the right value

noisesmith05:06:05

it's weird but it works

victora05:06:19

so not returns true... ?

noisesmith05:06:26

not nil returns true

noisesmith05:06:32

update gives you nil if the thing is not present

noisesmith05:06:40

so I think you actually just want assoc

victora05:06:44

oh so update recieves a function no ta value

noisesmith05:06:52

or, use a set instead of hash-map and use conj

noisesmith05:06:44

where "in the set" translates to true

noisesmith05:06:47

oh, I would definitely use assoc with true rather than update with not btw

noisesmith05:06:54

(if going that route)

victora05:06:27

It's certainly different

victora05:06:32

like I don't even know programming

noisesmith05:06:31

once it comes naturally, immutability has some nice side effects though 😉

noisesmith05:06:49

eg, making concurrency and parallelism a lot easier to get right for starters

victora05:06:40

are synchronization methods still necessary?

noisesmith05:06:11

we don't really use either

noisesmith05:06:31

refs / atoms do an optimistic update followed by compare and swap without locking

noisesmith05:06:55

you do need to watch out for side effecting functions in your update code, but that's easy enough to avoid

victora05:06:15

Interesting

victora05:06:08

Thanks for the help @john @akiroz and @noisesmith . I'm gonna get some sleep, but I'll come back with more questions 🙂

vitruvia17:06:47

what is the difference between (use 'namespace) and require?

noisesmith17:06:35

use brings in symbols unqualified, require only brings things in via :as or :refer keys, otherwise you need to specify the whole namespace to use it

noisesmith17:06:04

the strong preference in the community is to use require with :as instead of use or refer

vitruvia17:06:12

but is there a way of requiring a namespace without having to prefix it? Like in python "import library as *" lets you use functions from that library as if they were part of your current program.

vitruvia17:06:41

I mean that works with use so I'm wondering if it can be done with require. This is mostly for quick tests in my repl

noisesmith17:06:11

that's what use is for

noisesmith17:06:22

but don't do this in code, it makes things harder to read

vitruvia17:06:07

I'll keep use for simple tests then

noisesmith17:06:15

if you don't want the :as feature, or to limit which symbols get referred, there's no reason to use require instead of use

vitruvia17:06:47

Yes but I understand what you mean that in real code people should know where I'm getting those functions from

john17:06:57

If you need a specific function from a namespace: (require '[nspace.nfile :refer [specific-function]])

john17:06:27

Far more explicit than use, at least.

john17:06:06

But doing (require '[nspace.nfile :as nf]) and then (nf/specific-function ...) will eliminate a whole class of errors related to shadowing variables, etc.

john17:06:44

"I never would have thought another namespace would also use the name specific-function"

john17:06:51

They're usually compile time errors, so it's not that big a deal. But just settling on using :as is the simplest rule of thumb.

noisesmith17:06:45

the main problem with use and the :refer feature of require is when reading code, or trying to refactor

john17:06:39

I only partially agree with that argument. I think that if a particular namespace or set of functions aren't exposed to downstream users, but are only for the purpose of other functions within a given lib, cluttering up the whole namespace with tons of prefixes doesn't make it more readable.

noisesmith17:06:03

that's the opposite of all my experience

noisesmith17:06:18

require never exposes anything to downstream users

john17:06:05

I mean, exposure in the sense of downstream users needing to read the upstream code and actually understand it.

noisesmith17:06:17

I don't want to play guessing games figuring out where the hell a function definition came from when reading the code

john17:06:48

If some util/read function is used everywhere in a lib... I think we'll get it pretty quick that that :refer [read] at the top is what it is.

noisesmith17:06:41

as someone who has to read and maintain code other people wrote, that kind of thing just bugs me

noisesmith17:06:53

I want the namespace that something came from to be 100% unambiguous

noisesmith17:06:28

without having to constantly jump to the ns form and look at the refer blocks

vitruvia17:06:40

so you prefer just (require x) then x/function?

john17:06:45

having 50 util/s up and down a file seems like too much to me. Now, if it was read-string that could def be ambiguous. Yeah, annoyance is a very valid argument and I may be shying away from :refer too, just because of culture, mostly.

noisesmith17:06:19

@vitruvia with :as, so (ns my.ns (:require [some.other.ns :as other])) (other/foo ...)

john17:06:17

You could do (ns my.ns (:require [some.other.ns])) (some.other.ns/foo ...) Your code might get bloated though.

noisesmith17:06:53

it's also a bad habit because it can lead to forgetting to require the ns (it works anyway, as long as some other ns required it...)

noisesmith17:06:23

so you get no error during dev, but then with a fresh restart it errors, or later when you change the other namespace it errors

john17:06:39

right. (some.other.ns/foo ...) won't run unless the namespace is required somewhere.

noisesmith17:06:01

but the specific problem is it does work even without a require in your ns... but by accident. That's the real danger.

john17:06:19

yup. I've been bit.

noisesmith17:06:33

see for example people that think that clojure.set is always in scope, because lein's repl requires it

noisesmith17:06:50

(or pprint for that matter)

john17:06:58

There's also that issue I ran into recently with :refer in CLJS, where if you want to require multiple different implementations of a particular protocol, each instance method is going to have the same name, which won't work with :refer.

noisesmith17:06:53

you should never be getting the instance method from the implementation

noisesmith17:06:58

that's the wrong way to use protocols

noisesmith17:06:06

the method comes from the ns defining the protocol itself

noisesmith17:06:48

that's kind of the point of protocols, it's one method that does different things on each arg depending on how that arg extends the protocol

john17:06:48

I was making a type that took multiple objects of different types during construction. I wasn't doing the vanilla protocol thing. Sort of an implementation inheritance. So I think I needed to. But I'd have to go back and look at the code. You helped me with it at the time.

noisesmith17:06:04

if you had two protocols that had methods of the same name, you could have an issue like this... but you can't even require a protocol method from an implementation namespace last I checked, it has to come from the ns defining the protocol

noisesmith17:06:07

@john full example demonstrating what I'm saying

+user=> (ns some.proto)
nil
+some.proto=> (defprotocol IFoo (frob [this]))
IFoo
+some.proto=> (ns some.impl (:require [some.proto :as p]))
nil
+some.impl=> (deftype Foo [] p/IFoo (frob [_] "hi"))
some.impl.Foo
+some.impl=> (ns some.client (:require [some.proto :as p] [some.impl :as i :refer [frob]]))
IllegalAccessError frob does not exist  clojure.core/refer (core.clj:4201)
+some.client=> (ns some.client (:require [some.proto :as p] [some.impl :as i]))
nil
+some.client=> (p/frob (i/->Foo))
"hi"

sakalli17:06:29

hi all, trying to set up nightcode for doing some arduino hacking with my son. can get the project to run in emacs. but on nightcode i do get the repl working. but when evaluating the lib for arduino on core.clj i get a filenotfoundexception on classpath. when trying to load the firmata lib. ie.

(require '[firmata.core :as fm])

sakalli08:06:42

just to share: chatted with Zach @U080181PF about this and learned that instarepl in nightcode can't access third party libraries but that nightlight's instarepl doesn't have that restriction. Checked out Nightlight and tried it as well. It's awesome and when evaluation of just a single expression is supported I think that might be the right tool for this project!

noisesmith17:06:23

are you sure the repl is using your project deps?

sakalli17:06:36

or. i think.

john17:06:02

@noisesmith That's right. I remember now.

noisesmith17:06:11

what does (System/getProperty "java.class.path") show?

noisesmith17:06:41

it should show all the jars you depend on, plus your source directories

noisesmith17:06:18

@sakalli sorry, if it wasn't clear, that question was directed to you ^

sakalli17:06:49

yes cheers @noisesmith checking as we speak.

sakalli17:06:51

cant find the firmata dependency in classplath but the require does evaluate in repl:

sakalli17:06:33

and this is how the insaREPL looks like:

vitruvia17:06:04

Do you guys use vim for clojure?

noisesmith17:06:50

@sakalli one thing that looks weird - it almost looks like your project.clj is inside the target directory? it could be I'm misunderstanding the nightcode UI

sakalli18:06:42

see what you mean, but no. its just the ui. target and project.clj on the same level.

noisesmith18:06:12

it seems very odd to me that the require would work in the repl and not your source file

noisesmith18:06:18

within the same environment

sakalli18:06:46

do not know nightcode, so prolly something there.

sakalli18:06:52

how im using it.

curlyfry18:06:57

@vitruvia I use Cursive with ideavim

sakalli18:06:17

@vitruvia use evil-mode (vim keybindings) within emacs

lilactown19:06:42

I'm just getting started with emacs (save me 😱)

mobileink19:06:12

have faith. The One True Editor will save you.

noisesmith19:06:55

+user=> (ffirst (drop-while #(apply not= %) (partition 2 1 (iterate #(pprint/cl-format nil "~r" (count %)) "one"))))
"four"
fun fixed point

mobileink19:06:06

my head hurts, thanks. obfuscated clojure contest, anybody?

noisesmith19:06:14

it's not obfuscated!

noisesmith19:06:30

for each input, it counts the letter's and prints it in english

mobileink19:06:40

it obs my fustics.

noisesmith19:06:52

it stops when two in a row are the same - a fixed point number that has as many letters in it as it denotes

noisesmith19:06:21

if you just looked at the iterate, it would get to "four" and just repeat that - because "four" has four letters

mobileink19:06:54

can it be written in a form that makes the meaning obvious?

mobileink19:06:24

ok, "obfuscatory", then. 😉

noisesmith19:06:38

+user=> (->> "one"
             (iterate #(pprint/cl-format nil "~r" (count %)))
             (partition 2 1)
             (drop-while #(apply not= %))
             (ffirst))
"four"

noisesmith19:06:51

+user=> (take 10 (iterate #(pprint/cl-format nil "~r" (count %)) "one hundred"))
("one hundred" "eleven" "six" "three" "five" "four" "four" "four" "four" "four")

mobileink19:06:09

(blow (mind 'mobileink))

john20:06:05

ya dat cray yo

john20:06:56

oh I see

noisesmith20:06:26

+user=> (take 10 (iterate #(pprint/cl-format nil "~r" (count %)) (pprint/cl-format nil "~r" (rand-int 1000000000))))
("five hundred seventy-three million, nine hundred ninety-seven thousand, eight hundred ninety-six" "ninety-six" "ten" "three" "five" "four" "four" "four" "four" "four")

mobileink20:06:13

cray-cray. i loves it.

mobileink20:06:35

take that, beginners!!!

vitruvia23:06:52

so clojure already has a function to print numbers in english? I remember doing dictionaries to solve a similar thing in python

noisesmith23:06:55

cl-format is a port of the common lisp format function, which can do a lot of crazy stuff, including printing english names for numbers, roman numerals, complex turing complete formatting rules...