Looping through args of macro - loops

I am trying to write a macro in Clojure that allows for evaluation of a series of simple "def" expressions. I am a n00b when it comes to macros. The idea is that
(my-defs y1 1
y2 "taco")
should expand to
(do (def y1 1) (def y2 "taco"))
The following code accomplishes this for the special case of two defs
(defmacro my-defs
[& args]
`(do
(def ~(first args) ~(second args))
(def ~(nth args 2) ~(nth args 3) )))
which is nice, but I am having trouble generalizing this. I tried out a few naive things involving looping through bindings of the elements of (partition 2 args) but I always got garbage (I know this isn't very specific but the diversity and extent of the garbage seemed a bit too much to report here). How do I loop over these are and evaluate my defs?
P.S.
The macro my-defs is a toy. What i really want to accomplish in the end is a littel helper macro to instantiate a bunch of multimethod instances. Currently I have large chunks of code that look like
(defmethod f [A B] [x] "AB")
(defmethod f [A A] [x] "AA")
(defmethod f [C B] [x] "CB")
which is a little unsightly. It would be nice if I could do something like
(defmethods f
[A B] [x] "AB"
[A A] [x] "AA"
[C B] [x] "CB")
instead.

It looks to me like you're looking for the ~# macro expansion/unquote.
(defmacro defmethods [n & defs]
`(do ~#(map (fn [[a1 a2 a3]]
`(def ~n ~a1 ~a2 ~a3))
(partition 3 defs))))

Related

Locally rebinding `+`

How to translate this Clojure code to Hy, so it prints 2?
It doesn't need to be like Clojure, i just want to hide + and replace it with - in local environment.
(defmacro q [expr]
`(let ~'[+ (fn [x y] (- x y))]
~expr))
(print (q (+ 3 1)))
In Clojure it prints 2 (let creates a local environment).
In Hy it prints 4.
How to make Hy print 2 also, by replacing the + with - ?
I need those local environments because i am making a DSL.
This doesn't do what you expect in Hy because + is a macro, and macro calls take precedence over function calls:
(defmacro x [] 1)
(defn x [] 2)
(print (x)) ; => 1
Your options are:
Instead of +, use a name doesn't have the same name as a core macro, like my+ or +2.
Only use your new + in contexts other than the head of an Expression (which is the only place Hy expands macro calls), such as (map + (range 10)).
In q, replace the symbol + in the input instead of just setting the variable +, as in something like
(defmacro q [expr]
(import hyrule [coll?])
(defn f [x]
(cond
(= x '+) '-
(coll? x) ((type x) (map f x))
True x))
(f expr))
(print (q (+ 3 1)))
Use defmacro to define a new macro named +. This is a bad idea because you lose access to the original + in this module, including in the expansions of macros you didn't write that expect + to have its usual meaning. Local macros are not yet implemented (#900).

Is there a better way of writing nested loops in clojure?

Is there a better way of implementing nested loops in clojure?
As a beginner I have written this code of nested loop for comparing difference between dates in days.
Comparing this with nested loops in java using for or while.
(def my-vector [{:course-type "clojure"
:start-date "2021-01-25"
:end-date "2021-02-06"}
{:course-type "r"
:start-date "2021-01-15"
:end-date "2021-02-06"}
{:course-type "python"
:start-date "2020-12-05"
:end-date "2021-01-05"}
{:course-type "java"
:start-date "2020-09-15"
:end-date "2020-10-20"}
])
(defn find-gap-in-course [mycourses]
(println "Finding gap between dates....")
(loop [[course1 & mycourses] mycourses]
(loop [[course2 & mycourses] mycourses]
(when (and
(and (not-empty course1) (not-empty course2))
(> (-> java.time.temporal.ChronoUnit/DAYS
(.between
(LocalDate/parse (course2 :end-date))
(LocalDate/parse (course1 :start-date)))) 30))
(println "Dates evaluated are =" (course2 :end-date) (course1 :start-date))
(println "Gap of > 30 days between dates ="
(-> java.time.temporal.ChronoUnit/DAYS
(.between
(LocalDate/parse (course2 :end-date))
(LocalDate/parse (course1 :start-date)))))
(do true)))
(do false)
(if course1 (recur mycourses))))
(find-gap-in-course my-vector)
Learning to program in Clojure requires that one learn to think a bit differently because the tricks and techniques which people become accustomed to using in imperative programming may not serve as well in Clojure. For example in a nested loop, such as you've shown above, what are you trying to do? You're trying to match all of the elements of mycourses against one another and do some processing. So let's define a function which gives us back all the combinations of elements in a collection 1:
(defn combos[c] ; produce all combinations of elements in a collection
(for [x c y c] (vector x y)))
This is a very simple function which matches all the elements of a collection against one another and returns the accumulated pairings. For example, if you invoke
(combos [1 2 3])
you'll get back
([1 1] [1 2] [1 3] [2 1] [2 2] [2 3] [3 1] [3 2] [3 3])
This will work with any collection. If you invoke combos as
(combos '("abc" 1 [0 9]))
you'll get back
(["abc" "abc"] ["abc" 1] ["abc" [0 9]] [1 "abc"] [1 1] [1 [0 9]] [[0 9] "abc"] [[0 9] 1] [[0 9] [0 9]])
So I think you can see where we're going here. Rather than running a nested loop against a collection, you can just create a collection of combinations of elements and run a simple loop over those combinations:
(defn find-gap-in-course [mycourses]
(loop [course-combos (combos mycourses)]
(let [combi (first course-combos)
[course1 course2] combi]
; ...processing of course1 and course2 here...
(recur (rest mycourses)))))
But what if we don't want to consider the cases where a course is matched against itself? In that case another function to only return the desired cases is useful:
(defn different-combos [c] ; all combinations where [1] <> [2]
(filter #(not= (% 0) (% 1)) (combos c)))
Use whatever works best for you.
1 About here the Clojure cognoscenti are probably screaming "NO! NO! Use clojure.math.combinatorics!". When teaching I like to give useful examples which the student can see, read, and learn from. YMMV.
Here is how I would write the above code, starting from my favorite template project. I have included some unit tests to illustrate what is occurring in the code:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:import
[java.time LocalDate]))
(defn days-between
"Find the (signed) interval in days between two LocalDate strings."
[localdate-1 localdate-2]
(.between java.time.temporal.ChronoUnit/DAYS
(LocalDate/parse localdate-1)
(LocalDate/parse localdate-2)))
(dotest ; from tupelo.test
(is= -5 (days-between "2021-01-25" "2021-01-20"))
(is= 5 (days-between "2021-01-25" "2021-01-30")))
(defn course-pairs-with-30-day-gap
"Return a list of course pairs where the start date of the first course
is at least 30 days after the end of the second."
[courses]
(for [c1 courses
c2 courses
:let [start-1 (:start-date c1)
end-2 (:end-date c2)
gap-days (days-between end-2 start-1)]
:when (< 30 gap-days)]
[(:course-type c1) (:course-type c2) gap-days]))
with result
(dotest
(let [all-courses [{:course-type "clojure"
:start-date "2021-01-25"
:end-date "2021-02-06"}
{:course-type "r"
:start-date "2021-01-15"
:end-date "2021-02-06"}
{:course-type "python"
:start-date "2020-12-05"
:end-date "2021-01-05"}
{:course-type "java"
:start-date "2020-09-15"
:end-date "2020-10-20"}]]
(is= (course-pairs-with-30-day-gap all-courses)
[["clojure" "java" 97]
["r" "java" 87]
["python" "java" 46]])))
In the output, I left the names of course-1, course-2, and the gap in days to verify the calculation is the intended one. This could be modified or extended for production use, of course.
In clojure we normally use pre-existing functions like for (technically a macro) instead of low-level tools like loop/recur. The modifiers :let and :when make them extra-powerful for analyzing & transforming data structures.
Please see this list of documentation sources,
especially books like Getting Clojure and the Clojure CheatSheet.

Is there a way in the hy language to use doto on self?

hopefully someone can help me with this hy question. I am porting some python code over to hy, and was trying to figure out how I could remove some repetitive code using the doto macro. For example, look at a python class like this:
class Foo(object):
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
How could I convert this in hy to use doto?
(defclass Foo [object]
[[__init__ (fn [self x y z]
(doto self ;
(setv ...) ; What goes here?
))]])
The problem is that it looks like you normally do something like this:
(defclass Foo [object]
[[__init__ (fn [self x y z]
(setv self.x x)
(setv self.y y)
(setv self.z z))]])
I don't see a way of using (doto) on self.
That is an interesting idea. You can do this:
(doto self
(setattr "x" x)
(setattr "y" y)
(setattr "z" z))
But it's not much better. Consider defining a macro:
(defmacro vars-to-attrs [obj &rest attrs]
(let [[actions (list (map
(fn (a) `(setattr (str '~a) ~a))
attrs))]]
`(doto ~obj ~#actions)))
And then calling it like this:
(vars-to-attrs self x y z)
This might work better as a function though:
(defun vars-to-attrs-fun [obj &rest attrs]
(for [a attrs]
(setattr obj a (get (locals) a))))
And then call it like:
(vars-to-attrs-fun self 'x 'y 'z)
or, equivalent:
(vars-to-attrs-fun self "x" "y" "z")
If you just want to keep __init__'s locals, the easiest way is to directly .update the instance vars with the local vars.
(defclass Foo [object]
(defn __init__ [self x y z]
(.update (vars self) (vars))))
(By the way, the above is using our new defclass syntax from the version of Hy on Github, which won't work with the current PyPI version. [Update: it's now in the current PyPI release])
This does include all the locals, so you get a self.self, which is probably harmless, but you can del it after if you want. Hy sometimes generates locals to make statements act like expressions. These could also end up in the instance dict if you're not careful. You can avoid this by associng only the names you want:
(assoc (vars self)
'x x
'y y
'z z))
The new setv syntax also takes an arbitrary number of pairs, so you could do something like this instead:
;; new setv syntax
(setv self.x x
self.y y
self.z z)
You could pretty much do this before using tuples:
;; works in both Hy versions
(setv (, self.x self.y self.z)
(, x y z))
You can also avoid duplication in .update with a dict-comp, though this isn't usually shorter.
(.update (vars self) (dict-comp k (get (vars) k) [k '[x y z]]))
If you're still set on using doto, the correct syntax is:
(doto self
(-> (. x) (setv x))
(-> (. y) (setv y))
(-> (. z) (setv z)))
This does avoid repeating self, but it's not shorter than the aforementioned alternatives, so doto is the wrong tool for this particular job.
Update
I've made an issue for this https://github.com/hylang/hy/issues/1532
We might be adding an attach macro to Hy. I also posted an implementation if you want to try it out early.
Usage:
(defclass Foo []
(defn __init__[self x y z]
(attach self x y z)))
Since the attachment target is the first argument, attach would also work in a -> or in a doto, e.g.
(doto self
(.configure foo bar)
(attach spam eggs))

Write a nested doseq over unknown number of collections

I have a file LIST that has a sequence of characters per line. Each line is labeled with a category, i.e. "C". Example:
C: w r t y i o p s d f g h j k l z b n m
V: a e i o u
E: n m ng
I want to print every combination of C, V and E (or maybe just C and V, C and E, etc.) using doseq, but generically as I won't know the nested collections at compile time.
I.e.
"CV" [x y] (str x y )
"CVE" [x y z] (str x y z)
"CVCV" [x y z a] (str x y z a)
My code word-generator.clj
(ns word-generator )
(use 'clojure.string)
(import 'java.io.File)
(use 'clojure.java.io)
(defn get-lines [fname]
(with-open [r (reader fname)]
(doall (line-seq r))))
(defn get-list [x lines]
(first (remove nil?
(for [num (range (count lines)) ]
(if (= (first(split (nth lines num) #"\s+")) x)
(rest(split (nth lines num) #"\s+")))))))
(def sounds(get-lines "LIST")) ;; get the list
(def C (get-list "C:" sounds)) ;; map consonants
(def V (get-list "V:" sounds)) ;; map vowels
(def E (get-list "E:" sounds)) ;; map end consonants
(def LI "CVE") ;; word structure
(defn word-runner[carry args depth]
(doseq [x C y V z E] (println (str x y z)))) ;; iterate and make the words
(defn runner[]
( (print "enter arg list: ")
(def INPUT (read-line))
(word-runner "" INPUT 0)))
How can I implement word-runner so that doseq does a nested loop over all sequences of characters found in the file - but without knowing the number of lines in the file at compile-time?
This is actually a problem of combinatorics, not so much looping. Use the cartesian-product function from the math.combinatorics library to solve your problem.
;; alternative implementation of "word-runner"
(defn print-cartesian-products [& seqs]
(doseq [combs (apply cartesian-product seqs)]
(println (apply str combs))))

Parsing of nested array structure

I'm trying to parse a nested array structure of the following form:
[element [[child1] [child2] [child3 [[subchild1] [subchild2]]]]]
I would also like to return a list with all symbols (and nothing else), regardless of nesting depth; however, I'm not looking for flatmap or flatten etc, since I need to perform more complicated additional work on every element.
This is what I came up with so far:
(defn create-element [rs element]
(if (symbol? element)
(cons element rs)
rs))
(defn parse
([rs element] (create-element rs element))
([rs element [children & more]] (if (nil? more)
(parse (parse rs element) (first children))
(parse (parse rs element) (first children) more))))
(defn n-parse [element]
(apply parse () element))
This works fine for the following input:
=> (n-parse ['bla [['asd] ['kkk] ['sss]]])
(sss kkk asd bla)
But this doesn't work:
=> (n-parse ['bla [['asd] ['kkk [['ooo]]] ['sss]]])
(sss kkk asd bla)
I'm still trying to wrap around my head around the types but can't seem to manage to get it right. For example, Haskell makes this easy with pattern matching etc, whereas Clojure doesn't allow same arity function overloading.
Also is there a more concise / idiomatic way (without having to resort to if?) I'd prefer pure Clojure solutions (no external libs) since this is actually for a Clojurescipt project.
Many thanks for any help!
I don't see whats wrong with flatten. If you want to do some work on the items first, do the work first and then flatten the result:
(defn map-tree
"Example: (map-tree + [1 2 [3 5]] [3 4 [5 6]])"
[f & trees]
(if (every? coll? trees)
(apply map (partial map-tree f) trees)
(apply f trees)))
(defmulti transformator identity)
;; transform 'sss element into something special
(defmethod transformator 'sss [_] "sss")
;; default transformation
(defmethod transformator :default [v] v)
Test:
user> (flatten (map-tree transformator '[bla [[asd] [kkk] [sss]]]))
(bla asd kkk "sss")
user>
Would that not work?

Resources