Fork me on GitHub
#beginners
<
2023-03-17
>
Zach λ05:03:31

Hey all, my Github is empty so I'm looking to build a couple projects as work samples in case the right job opportunity comes up. Does anyone have any interesting project ideas that are somewhat creative / not stereotypical / have some challenge that would be good examples to stand out a bit?

phronmophobic05:03:33

If you're enterprising, using graalvm's native-image to build a clojure program targeting android should be fun. I wrote a similar project for iOS, https://github.com/phronmophobic/grease. I don't think there are any technical limitations. It's just a matter of elbow grease.

Zach λ05:03:19

I really like the idea, but I don't have access to any android devices, so it would probably be easiest to stick to something either related to general CS or web-programming. that is way cool, though

👍 2
phronmophobic06:03:33

An old idea that I think is just waiting for someone to pick it up and run with it is codeq, https://blog.datomic.com/2012/10/codeq.html

👍 2
Danilo Oliveira06:03:07

I spent like one year in the same dilemma. Now I'm doing an online chess game like lichess :man-shrugging::skin-tone-2:

Danilo Oliveira06:03:41

It's better any idea than paralyzed trying to find the best idea. My Clojure is much stronger after I started working on this chess game

Mark Wardle19:03:07

Advent of code? Quite fun and a nice challenge… and one learns a lot from trying… and then seeing how others have done it. Eg I saw value in eduction.

Mno14:05:28

Contributing to open source stuff also really helps, you can always pick up issues and stuff and try to offer a solution.

Joseph Graham06:03:42

Hi all. I have seen some code I don't understand. It looks like this: (:foo bar []) I tried in my repl to see what it does but it seems the empty vector has no effect that I can see:

(def farm {:duck "pond" :sheep "field"})
#'user/farm
(:duck farm)
"pond"
(:duck farm [])
"pond"

genmeblog07:03:23

Try (:foo farm [])

Joseph Graham07:03:00

right so it gives me control when they key is not found. Cool!

daveliepmann08:03:04

It's using the 2nd arity of https://clojuredocs.org/clojure.core/get: (get map key not-found)

skylize23:03:52

Nice find @U043HLWSYUQ. I had no idea that "keyword as a function" included the not-found arity.

Alejandro Buffery14:03:22

I am trying calva and doing the builtin tutorial. I have done the repl file and all has worked fine. I then open the the paredit file and it is not connected, so i do ctrl+alt+c enter as the instructions in the tutorial say, but it does not load the file and gives errors. When doing ctrl+alt+c i am presented with different options (Generic, Clojurescript, Babashka, …) I have tried choosing different options but it does not work. If important, I am on a mac m1. Thanks !

pez14:03:45

Hi! Let me try that myself first to make sure it works as I expect.

pez14:03:23

It works on my machine:tm:. What do you mean when you say that the paredit file is not connected?

Alejandro Buffery14:03:25

when i do ctrl+alt+c the repl editor has this message: ; Reading port file: file:///Users/alejandro/Documents/trash/clojure/.joyride/.nrepl-port ... ; No nrepl port file found.

Alejandro Buffery14:03:54

; No nrepl port file found. ; Bad url: localhost:

pez14:03:04

That doesn't look good at all. I think I might have messed something up. But before this you say that the the first tutorial file worked fine. Then: > I then open the the paredit file and it is not connected What do you mean by this?

Alejandro Buffery14:03:25

Sorry I explained wrongly. When I change from the repl tutorial to the paredit tutorial (i close the repl file) I find the repl on the right hand side editor correctly connected to paredit. But the first instruction in the paredit tutorial is : ;; Start with loading this file ;; Ctrl+Alt+C Enter So that is what i do, and that is when i lose connection and get this output in the repl on the right: ; Connecting ... ; Reading port file: file:///Users/alejandro/Documents/trash/clojure/.joyride/.nrepl-port ... ; No nrepl port file found. ; Bad url: localhost:

Alejandro Buffery14:03:25

When i close the buffer with the repl tutorial i have the paredit tutorial. In the paredit tutorial if i evaluate any form in the repl i get correct output with ‘clj:hello-paredit:>’ But when i do ctrl+alt+c is when everything stops working and i get the error messages

pez15:03:56

Thing is that nothing should happen on ctrl+alt+c. It is the first binding in a chord, You should see something like (ctrl+alt+c) was pressed, waiting for second key in chord in the status bar. If you then press enter, Calva should load the file, and the prompt should change in the output/repl window to the right. If you have time for a quick screen share you can DM me and I can try help you. It's a bit tricky just describing for each other like this.

Alejandro Buffery15:03:39

Got it working, thankkssss Pezzz !!! I have the vscode status bar hidden to gain screen space, I didn’t see the ‘waiting …’. so did not realise via the tutorial explanation that it was a chord with Enter. I mistakenly thought I was going to get a pop-up, confirmation or something where I had to give Enter. Seeing there was no response, i was pressing ctrl+alt+c again just in case i had pressed the 3 keys together wrongly, and that is when I was getting the popup options of (Generic, Clojurescript, etc) and that is where I thought I had to give the ‘Enter’, and that is when i started getting the error messages.

pez15:03:04

I see. That figures. Maybe the tutorial should start with some basic VS Code things, or at least let the reader know what they are assumed to know going in.

💯 2
pez15:03:07

But at the very least, we can introduce our ”syntax” for keyboard shortcuts.

Alejandro Buffery15:03:42

Maybe making explicit in the tutorial that it is a complete chord may help, because some combinations do bring up first a popup, like the vscode command pallette with cmd+p. And maybe for people that do not know what a chord is offer some quick explanation or maybe there an easy valid vscode tutorial / official tutorial / video explaining what is a chord that can be referenced with a simple link.

pez15:03:02

Thanks! I added some text there now at the start of the repl turtorial. If you fire up the getting started repl again and choose to download new files, you should see it. Let me know if you think it'll work. I didn't find anywhere where the VS Code team introduces chords, actually. Interesting.

Alejandro Buffery16:03:14

I would further simplify it: Original: ;; == Some VS Code knowledge required == ;; This tutorial assumes you know some few things about ;; VS Code. Please check out this page if you are new ;; to the editor: https://code.visualstudio.com/docs ;; ;; We use a notation for keyboard shortcuts, where ;; + means the keys are pressed at the same time ;; and separates any chords in the sequence. So: ;; Ctrl+Alt+C Enter means to press Ctrl, Alt, and C ;; all at the same time, then release the keys and ;; then press Enter. Proposed: • make 2 blocs of comments instead of one • eliminate the word ‘chord’ since not necessary for the explanation, and may confuse music students 😅 ;; == Some VS Code knowledge required == ;; This tutorial assumes you know some few things about ;; VS Code. Please check out this page if you are new ;; to the editor: https://code.visualstudio.com/docs ;; == Keyboard Shortcuts Notation used in this tutorial == ;; We use a notation for keyboard shortcuts, where ;; + means the keys are pressed at the same time ;; and separates key sequences into groups. So: ;; Ctrl+Alt+C Enter means to press Ctrl, Alt, and C ;; all at the same time, then release the keys and ;; then press Enter.

Alejandro Buffery16:03:15

… chords in music are notes played simultaneously, instead of as a sequence.

pez16:03:53

I have made the same observation. But it is what it is called in VS Code: Search for chords here: https://code.visualstudio.com/docs/getstarted/keybindings

skylize00:03:24

"Chord" is a VS Code term. But as mentioned above, they never explain it anywhere. I agree the term is confusing because the primary usage in music is for notes played at the same time, while Code uses it to refer to a sequence keybinding. (We do also use "chord" in reference to arpeggio, so it's not completely opposite, but still counterintuitive.) I support trying to come up with a better term, that encapsulates the concept more naturally, and then mostly dropping use of "chord". (In the meantime, that's what we've got.)

skylize00:03:50

I guess the first brainstorming idea came out by accident from my rant. > sequence keybinding

Alejandro Buffery07:03:31

It may be one of the cases where an image/video/gif is worth a thousand words 🎹

pez07:03:49

> as mentioned above, they never explain it anywhere I linked to where they explain it. Though it is in the context of defining keybindings. Doesn't seem like we win very much on discussing their choice of terms here, though. Bring it to them.

skylize13:03:39

If can we come up with a term that stands strong on its own, without the need for explaining the incongruity, we win plenty. We can use that term in Calva's docs and when offering support, regardless of what VS Code does. ... Then we also could campaign for change in Code's Issues page with a ready-made solution, instead of just complaining.

Roy Xue16:03:16

Hi everyone! A very newbie question. Why does let require a vector for binding not a list?

dpsutton16:03:57

I believe that it’s just a choice of syntax. I think it nicely adds a different symbol that lists but is still unambiguously a sequential grouping.

dpsutton17:03:21

contrast it with a more tradition syntax like the emacs lisp here:

(defun cider--find-var-other-window (var &optional line)
  "Find the definition of VAR, optionally at a specific LINE.

Display the results in a different window."
  (if-let* ((info (cider-var-info var)))
      (progn
        (if line (setq info (nrepl-dict-put info "line" line)))
        (cider--jump-to-loc-from-info info t))
    (user-error "Symbol `%s' not resolved" var)))
the bindings are in double nested (( paren delimiters. So the binding doesn’t stand out as much in my opinion. Contrast with a clojure vector syntax:
(defn cider--find-var-other-window
  [var line]
  (if-let [info (cider-var-info var)]
    (progn
      (if line (setq info ...))
      (cider--jump-to-loc))
    (user-error "symbol not resolved")))
the vector syntax shows the different meaning of the parens and is a tasteful addition in my opinion

emilaasa17:03:57

It's consistently used for bindings which I find readable.

ghadi17:03:33

parens in old lisps were overloaded for all sorts of purposes

dpsutton17:03:11

I think chicken scheme treats regular parens and square brackets as interchangeable. Not positive though

emilaasa17:03:41

Yeah I think that's true in several schemes - also the convention to use square brackets for argument lists I think is in a few other ones.

dpsutton17:03:30

So there have been lots of ideas about delimiters to provide readability. I think Clojure’s take on it is quite nice

☝️ 2
🌜 2
🌛 2
👍 2
2
Roy Xue17:03:55

Thank you all! All of the reasons are valid I think. Maybe it has to more to do with destructuring.

emilaasa17:03:44

It atleast makes for a simple parse target.

emilaasa17:03:34

But mostly for human parsing I think 🙂

escherize17:03:07

I was going to submit a patch for (let 🌘x 1:waxing_crescent_moon: x) But I just wrote a macro for it instead

💪 2
Michael W17:03:32

Is there an abstraction for when you want to use a map inside another map? For example rows and columns from a database, I run a map on the rows, then another map on the columns, and it feels wrong to do that. Is there a better way?

tschady17:03:21

vector (rows) of maps (cols)

pppaul18:03:07

depends on your data. maybe you could use for but i need to know more information. a code example may help.

pppaul18:03:02

one common way of dealing with nested structures in clojure is get-in, assoc-in, etc... also, sometimes it's convenient to make a list of queries into your complicated data-structure and map over those queries (via get-in)

pppaul18:03:36

if you only have the shape of the 2D array you are using, as in you know the row count and col count, then you can do something like this

pppaul18:03:12

(let [db {}]
  (->>
    (for [
          x (range 10) ;; rows
          y (range 10) ;; cols
          ]
      [x y])
    (map (fn [path]
           (let [datum (get-in db path)]
             ;; do work on DB cell
             datum
             )))))
this is probably less efficient than map in a map

Michael W18:03:44

Code example:

(def empty-board (vec (repeat 9 :empty)))
(def o-board [:o :o :o :x :x :o :x :empty :empty])
(def x-board [:x :x :x :o :o :x :o :empty :empty])
(def winning-rows [[0 1 2] [3 4 5] [6 7 8] ; horz
                   [0 3 6] [1 4 7] [2 5 8] ; vert
                   [0 4 8] [2 4 6] ; diag
                   ])

(defn winner
  "return winning [rowidx sym] or nil"
  [b]
  (->> (for [row winning-rows]
         (set (mapv #(get b %) row)))
       (map-indexed (fn [idx s]
                      (if (and
                           (#{1} (count s))
                           (not (:empty s)))
                        [(get winning-rows idx)  (first s)]
                        nil)))
       (filter some?)
       first))

(comment
  (winner empty-board) => nil
  (winner o-board) => [[0 1 2] :o]
  (winner x-board) => [[0 1 2] :x])

Michael W18:03:51

I started with a map instead of a for loop to initiate the threading. I'm just wondering if there is an abstraction here because this entire thing just feels awkward to me, that there should be a simpler way to do these operations.

pppaul18:03:12

probably, but your approach to the problem (how you shape your data) will lead to certain solutions

pppaul18:03:39

in your code, the for loop looks redundant

pppaul18:03:13

i suspect map-indexed may not be needed as well

Michael W18:03:54

I couldn't figure out a simpler way, it works but like I said it feels awkward.

pppaul18:03:46

(defn lookup-row [board [l1 l2 l3 :as row]]
  [
   (get board l1)
   (get board l2)
   (get board l3)
   ]
  )

(defn winner
  "return winning [rowidx sym] or nil"
  [board]
  (let [[first-win :as possible-wins] (for [row   winning-rows
                                            :let  [lookup (lookup-row board row)]
                                            :when (and
                                                    (not-any? #{:empty} lookup)
                                                    (or (every? #{:x} lookup)
                                                        (every? #{:o} lookup)))]
                                        {:id     row
                                         :lookup lookup})]
    first-win))

(comment
  (winner empty-board)
  ;;=> nil
  (winner o-board)
  ;;=> [[0 1 2] :o]
  (winner x-board)
  ;;=> [[0 1 2] :x]
  )
there is my attempt

pppaul18:03:26

i think that the for is being misused in my example, should just be a map into a filter. returning a map or something with meta data helps preserve information such as why someone won (what row won)

Michael W18:03:21

So return something like {:winner :x :rows [0 1 2]}?

pppaul18:03:19

so, i mentioned that there are different ways to approach organising the data for this problem. one way would be to not have a board, but instead just have points [{:x 0 :y 0 :mark :x}] and then you iterate over all those (built by the users as they take turns)

pppaul18:03:24

i didn't go as far as figuring out who won, my code returns something like {:id [0 1 2], :lookup [:o :o :o]} but the output is able to be run through a function that does figure out who won.

pppaul18:03:20

this is one of the nice things about clojure, keep your data around, as it doesn't have a huge penalty

pppaul18:03:54

meta helps if you want to hide your supporting data

Michael W18:03:22

I only made the board a simple vector of keywords to keep it in the simplest data structure. In the UI I also use a simple vector of 9 tiles, and a simple vector of 9 vectors that describe the history of the game. It makes it simpler in the state machine I am using to control game states by just using the indexes into the various vectors. I will think about what it would look like if I use maps instead.

Michael W18:03:57

Just to expand a bit in the state machine turn0 knows it's always going to populate the 0 index of the history vector. tile0 knows it will always be the 0 index of the board.

pppaul19:03:19

in this situation it's not better or worse. all approaches will have pros and cons. you can support both at the same time, though, by dispatching on some meta data (game-type or board-type). it may sound weird to do so in this context, but in normal business software this type of thing is very common.

Michael W19:03:39

Yes, I am trying to learn with this. Building it up from the simplest data structures is I think part of the learning process for me.

pppaul19:03:11

that's interesting. i'm not sure if the turn0 properties are so important. i guess if i know more about your code i can get a better idea of what you are trying to do with the sate machine

pavlosmelissinos19:03:51

How about this?

(defn winner [b]
    (->> (for [idxs winning-rows]
           (cond
             (every? #(= :x (get b %)) idxs) :x
             (every? #(= :o (get b %)) idxs) :o))
         (filter some?)
         first))

pavlosmelissinos19:03:18

or

(defn winner [b]
    (->> (for [idxs winning-rows]
           (cond
             (every? #(= :x (get b %)) idxs) :x
             (every? #(= :o (get b %)) idxs) :o))
         (some identity)))

Michael W19:03:51

That's perfect and a very elegant way to solve it. Thanks. All that wrangling with sets and maps inside for, etc just gone with a simple cond.

kennytilton20:03:01

I had:

(defn win? [board possible-win]
    (loop [[cell & more-cells] possible-win
           winner nil]
      (cond
        (nil? cell) (when winner [possible-win winner])
        :else (let [mark (get board cell)]
                (cond
                  (= mark :empty) nil
                  (nil? winner) (recur more-cells mark)
                  (= mark winner) (recur more-cells winner)
                  :else nil)))))
  (defn winner2 [board]
    (some #(win? board %) winning-rows))
One thing I like to do is divide and conquer, by breaking win? out as a fn. I like also that we could play with :a vs :z if we liked. Avoiding the set operation seemed efficient, though it is a natural way to think of a winning sequence. It also stops as soon as it sees an :empty or two different marks in a sequence.

Michael W20:03:51

The reason I liked sets is I could then expand to a 4x4 or 5x5 grid pretty easily and they always reduce to a set of 1 element if it's a winning row. To do that though I would have to figure out how to calculate the horiz, vert, diag for the winning rows of the grid size, instead of having them hard coded. I'll think on it more. Thanks all for the great examples of different ways to tackle this problem. I will be contemplating how to adapt my code in several ways now.

kennytilton20:03:35

Well, if we want to talk about generalizing, sure! 🙂 What we can do is specify valid steps instead of tediously enumerating all the wins. So conventional valid steps are [0,1], [1,0] [1,1] and [1,-1]. Left as an exercise!

Michael W20:03:47

Ok so that would be more of a board like this?

(def board [[:o :x :empty]
            [:x :x :o]
            [:o :x :empty]])
And we could describe the diagonal as [0,0][1,1][2,2] with each step being either +1 or -1 on a coord?

kennytilton20:03:30

Lemme look up the 2d array syntax I used for the job interview. brb...

kennytilton20:03:38

No, looks like I went with your vector of vectors:

(letfn
  [(winning-marker [candidate]
     ; Checks candidate col/row/diag for win and
     ; if so returns the winning marker, else nil
     (reduce (fn [x y]
               (when (= x y) x))
       candidate))

   (board-candidates [board]
     ; generates all rows,cols,and diags of a board
     (conj
       (concat
         board                                              ;; rows, in effect
         (apply map list board))                            ;; columns
       (map #(nth (nth board %) %)                          ;; tl to br diag
         (range 3))
       (map #(nth (nth board %) (- 2 %))                    ;; tr to bl diag
         (range 3))))]

  (defn tic-tac-toe-winner [board]
    (some winning-marker
      (board-candidates board)))

  (assert (= :x (tic-tac-toe-winner [[nil nil nil] [:o nil :o] [:x :x :x]])))
  (assert (= :x (tic-tac-toe-winner [[:x :o nil] [:x nil :o] [:x nil nil]])))
  (assert (= :x (tic-tac-toe-winner [[:x :o :x] [:x :o :o] [:x :x :o]])))
  (assert (= :o (tic-tac-toe-winner [[:o :x :x] [:x :o :x] [:x :o :o]])))
  (assert (nil? (tic-tac-toe-winner [[:x :o :x] [:x :o :x] [:o :x :o]]))))(letfn
  [(winning-marker [candidate]
     ; Checks candidate col/row/diag for win and
     ; if so returns the winning marker, else nil
     (reduce (fn [x y]
               (when (= x y) x))
       candidate))

   (board-candidates [board]
     ; generates all rows,cols,and diags of a board
     (conj
       (concat
         board                                              ;; rows, in effect
         (apply map list board))                            ;; columns
       (map #(nth (nth board %) %)                          ;; tl to br diag
         (range 3))
       (map #(nth (nth board %) (- 2 %))                    ;; tr to bl diag
         (range 3))))]

  (defn tic-tac-toe-winner [board]
    (some winning-marker
      (board-candidates board)))

  (assert (= :x (tic-tac-toe-winner [[nil nil nil] [:o nil :o] [:x :x :x]])))
  (assert (= :x (tic-tac-toe-winner [[:x :o nil] [:x nil :o] [:x nil nil]])))
  (assert (= :x (tic-tac-toe-winner [[:x :o :x] [:x :o :o] [:x :x :o]])))
  (assert (= :o (tic-tac-toe-winner [[:o :x :x] [:x :o :x] [:x :o :o]])))
  (assert (nil? (tic-tac-toe-winner [[:x :o :x] [:x :o :x] [:o :x :o]]))))
If we lose those hard-coded (range 3)s we can do your nxn boards. But I know I did a 2d array for some job quiz. brb...

kennytilton20:03:18

Oh, not using steps there ^^^ Still left as an exercise! 🙂

Michael W20:03:16

Jeez this is a job interview type question? I just thought it was an easy enough game to learn statecharts.

kennytilton20:03:46

(aget (to-array-2d [[01 2][3 4 5][6 7 8]]) 1 1) => 4

kennytilton21:03:43

The "question" was actually a request for a code review of a weak solution, with an optional invite to rewrite. The harder question was generatting unique internal triangles. Had a blast with that. But when we got to reviewing solutions they said they had googled the answer. Got a big kick out of that. And they have no idea how much fun they missed! https://gist.github.com/kennytilton/7571a6d0d5b1e862b00333b961df8e50

Michael W21:03:33

I have certainly had a blast so far just figuring out things in my own way. I am thinking about how to take the more general approach so I can make the game more interesting than just 3x3. Thanks again for the examples, I will be going back over these and evaluating how I am handling things.

pppaul20:03:03

I'll post something in a bit. for a 1d array, you use the col length (you have to define that somewhere), assume a square for the shape of the virtual 2d array. diagonal: start at (0, 0), next y value = prev + 1, next x Val = col length * & y Val + y val. I'm not 100% on this, but I think it's near tho solution. for the second diagonal, you are starting from (col length, 0), and the Arithmetic is a bit different.

pppaul22:03:30

(defn diagonal-L->R [board-length]
  (let [board-volume (* board-length board-length)]
    (vec
      (map +
           (range 0 board-volume board-length)
           (range board-length)))))

(diagonal-L->R 3)
;; => [0 4 8]

(defn diagonal-R->L [board-length]
  (let [board-volume (* board-length board-length)]
    (vec
      (reverse
        (map +
             (range board-volume 0 (- board-length))
             (range (- board-length) 0))))))

(diagonal-R->L 3)
;; => [2 4 6]
here is what i cam up with for calculating diagonals

peterh20:03:28

I just noticed that default values in map destructuring do not change the actual map but only apply to the associated binding:

(let [{:keys [x y] :or {x "a" y "bar"} :as m} {:y "foo"}]
    [[x (:x m)] [y (:y m)]])
;;=> [["a" nil] ["foo" "foo"]]
In this example, x will be bound to the default value "a", but the value for :x will remain nil. This behaviour caught me by surprise as I was passing option maps down in successive function calls and realized that my defaults did not propagate. It makes sense now that I think about it, but I was not expecting it, so I wondered if my realization might help someone to prevent the same mistake (but maybe it’s just me).

dpsutton21:03:48

it’s a good call out. Destructuring is not about changing any object flowing through the system. It’s only about helping you create bindings

4
skylize00:03:38

Another example of Clojure's immutable data structures. Destructuring is equivalent to using get. Has no effect on the source data.

Erik Colson23:03:31

hi, is there some kind of list of available libraries for Clojure? something like CRAN, CTAN etc would be awesome.

seancorfield23:03:50

Bear in mind that you can also use any of the many thousands of Java libraries available on Maven Central as well -- part of Clojure's power as a hosted language is being able to leverage the entire Java ecosystem as well.

2
seancorfield23:03:28

There's a channel here called #CQT1NFF4L that can be helpful if you're looking for a library to solve a specific problem.

phronmophobic23:03:07

Clojure also has pretty good access to javascript libraries, c libraries, and python libraries. I haven't tried them, but I think DART and CLR libraries can also be used from clojure if needed.

Erik Colson08:03:14

Thanks for all these hints!