Fork me on GitHub
#yamlscript
<
2024-05-19
>
Ingy döt Net01:05:58

@danielmartincraig continuing a bit on that "lesson" from the other day where these were equivalent...

a: b c d
a b: c d
this is the "normal" case but not the rule. Consider cond... It begs to be written:
cond:
  a > 5: b
  a > 10: c
  :else: d

Ingy döt Net01:05:33

Let's see it compiled:

$ ys -ce '
cond:
  a > 5: b
  a > 10: c
  :else: d
'
(cond (> a 5) b (> a 10) c :else d)

Ingy döt Net01:05:50

compare to:

$ ys -ce '
foo:
  a > 5: b
  a > 10: c
  :else: d
'
(foo ((> a 5) b) ((> a 10) c) (:else d))

Ingy döt Net01:05:25

compare to:

$ ys -ce '
foo %:
  a > 5: b
  a > 10: c
  :else: d
'
(foo (> a 5) b (> a 10) c :else d)

Ingy döt Net01:05:57

Note the %: . It gives you cond semantics

Ingy döt Net01:05:15

but I special case cond so you don't have to

Ingy döt Net01:05:37

It's part of a work in progress for YS to have a macro like system where anyone can define how a YAML structure/content gets turned into a specific clojure AST

Ingy döt Net01:05:54

for now some things are hard coded but not for long

Ingy döt Net01:05:47

... ;; end of mini lesson for today

Ingy döt Net01:05:14

I swear I'll write those docs soon

🚀 2
Daniel Craig03:05:57

Thanks much appreciated!

Ingy döt Net15:05:35

I forgot to mention:

=>: a b c d
a: b c d
a key of => is ignored. This is quite important as you'll often need to write a key pair but only be concerned with a single value.
$ cat a.ys 
!yamlscript/v0

say:
  case ENV.USER %:
    ."alice": 1
    ."bob": 2
    ."ingy": 3
    =>: 999
or:
$ cat b.ys 
!yamlscript/v0

defn add(a b):
  c =: a + b
  if (c = 0):
    die: 'Zero is evil'
  =>: c

Ingy döt Net15:05:26

Interesting CLI point. You can use b.ys above by adding a -e expression after it:

$ ys b.ys -e 'add: 3 3' -p
6
$ ys b.ys -e 'add: 3 -3' -p
Error: Zero is evil

Ingy döt Net15:05:17

I just remembered a cool YS feature that I was reminded of watching an old @alexmiller video on youtube. Alex shows:

(defn exp [x n]
  (apply * (repeat n x)))
In YS you can get apply semantics by splatting (`*` after symbol) any argument:
defn exp(x n):
  v =: repeat(n x)
  .*: v*
compiles to:
$ ys -e '
defn exp(x n):
  v =: repeat(n x)
  .*: v*
' -c
(defn exp [x n] (let [v (repeat n x)] (apply * v)))
and runs as:
ys -e '
defn exp(x n):
  v =: repeat(n x)
  .*: v*
' -e 'exp: 10 3' -p
1000
but wait... there's more!

Ingy döt Net15:05:13

Unlike apply you you don't need the splat in the last arg position, and you can also use multiple splats!

$ ys -e '
defn exp(x n):
  v =: repeat(n x)
  .*: 4 v* 2 v*
' -c
(defn exp [x n] (let [v (repeat n x)] (apply * 4 (concat v [2] v))))
runs:
$ ys -e '
defn exp(x n):
  v =: repeat(n x)
  .*: 4 v* 2 v*
' -e 'exp: 10 3' -p
8000000

Ingy döt Net15:05:18

Reminder the . in .* is ignored. Without it it would be invalid YAML (thus invalid YS)

Ingy döt Net15:05:42

There's one problem I uncovered though... This doesn't compile/work as expected:

$ ys -e '
defn exp(x n):
  .*: repeat(n x)*
' -c
(defn exp [x n] (* (repeat n x) *))
ie The splat * must currently follow a symbol. 😞

Ingy döt Net15:05:20

But I expect it will work in the next YS release!!! 🙂

Ingy döt Net16:05:08

In conclusion I think splatting is up there with a =: b lets in keeping YS code very clean; esp given that let and apply are so constantly used in Clojure coding.

Ingy döt Net16:05:48

Example:

$ ys -e '
defn foo(xs):
  y =: bar(xs*)
  inc: y
  z =: baz(xs* xs*)
  if (y > z): y z
' -c | zprint '{:width 30}'
(defn foo
  [xs]
  (let [y (apply bar xs)]
    (inc y)
    (let [z (apply baz
              (concat xs xs))]
      (if (> y z) y z))))

phill23:05:33

Nifty! But (slightly off-topic) beware of compiling to concat! https://stuartsierra.com/2015/04/26/clojure-donts-concat

Ingy döt Net23:05:14

Good to know. Thanks!

Ingy döt Net23:05:51

Nobody has ever reviewed my Clojure so if you're feeling ambitious 😜

Ingy döt Net00:05:44

Btw I only use concat when the splat is not in the final position

Ingy döt Net10:05:51

@U0HG4EHMH I read https://stuartsierra.com/2015/04/26/clojure-donts-concat and the linked https://groups.google.com/g/clojure-dev/c/ewBuyloeiFs Could you or someone write a version of concat using that solution? The page links to https://gist.github.com/jondistad/2a4971fe8948ca2f6ba0 but that is now 404. I can't seem to load http://archive.org from where I am...

phill11:05:15

Unless the lazy aspect is necessary, you can (reduce into [] seq1 seq2 ...)

Ingy döt Net11:05:02

The lazy aspect is not necessary. I'll give it a try. Cheers.

Ingy döt Net11:05:21

You might also be able to help me with a related irritation.

Ingy döt Net11:05:34

Give me a sec to review it

Ingy döt Net11:05:43

$ ys -e 'for [i (1 .. 5)]: say(i)'
$ ys -e 'each [i (1 .. 5)]: say(i)'
1
2
3
4
5
$
for is lazy so I made up ys::std/each to make it non-lazy. See: https://github.com/yaml/yamlscript/blob/main/core/src/ys/std.clj#L174-L177 What I really want to do is replace clojure::core/for with a ys::std/for that does what each does. But I can't seem to do it. Maybe because clojure::core/for is a macro and I don't know how to make a macro that calls a macro. Dunno why... BTW I've replaced a handful of clojure.core things with ys.std things but I have a ys.clj library so that one can always use clj/foo if they need the real foo https://github.com/yaml/yamlscript/blob/main/core/src/ys/clj.clj

Ingy döt Net11:05:45

I wonder if you can help me turn ys.std/each into ys.std/for

Ingy döt Net11:05:03

yes, but I really want it to be called for in YS 😕

Ingy döt Net12:05:05

I've been trying again. Will post my findings soon

Ingy döt Net12:05:32

Hey! It works now with for calling doseq instead of clojure.core/for

+ git diff
diff --git a/core/src/ys/std.clj b/core/src/ys/std.clj
index 27675ca..2893ab9 100644
--- a/core/src/ys/std.clj
+++ b/core/src/ys/std.clj
@@ -15,7 +15,8 @@
    [ys.ys :as ys]
    [yamlscript.common :as common]
    [yamlscript.util :as util])
-  (:refer-clojure :exclude [num
+  (:refer-clojure :exclude [for
+                            num
                             print
                             when]))
 
@@ -171,11 +172,16 @@
 (defn dirname [& args]
   (apply util/dirname args))
 
-(defmacro each [bindings & body]
+(defmacro for [bindings & body]
   `(do
-     (doall (for [~@bindings] (do ~@body)))
+     (doall (clojure.core/doseq [~@bindings] (do ~@body)))
      nil))
 
+(comment
+  (macroexpand-1 '(for [x (range 10)] (println x)))
+  ;; (do (clojure.core/doall (clojure.core/doseq [x (range 10)] (do (println x)))) nil)
+  )
+
 (defn err [& xs]
   (binding [*out* *err*]
     (apply clojure.core/print xs)

Ingy döt Net12:05:27

When I replaced doseq above with for I got a stack overflow.

Ingy döt Net12:05:12

No idea why I didn't try doseq this way before... Thanks a lot!!

Ingy döt Net12:05:21

But now when I try to add

(intern 'ys.clj 'for       clojure.core/for)
to clj.clj I get
Can't take value of a macro: #'clojure.core/for

Ingy döt Net12:05:32

Weird, I tried:

(intern 'ys.clj 'for       (clojure.core/resolve 'clojure.core/for))
and that compiles for I get a runtime error:
Error: Could not resolve symbol: i

Ingy döt Net12:05:17

Can you not intern a macro?

Ingy döt Net12:05:50

ok now I get:

$ YS -e 'clj/for [i (1 .. 5)]: println(i)' -c
(clj/for [i (rng 1 5)] (println i))
$
looks good
$ YS -e 'clj/for [i (1 .. 5)]: println(i)'
$
looks good. lazy, no output
$ YS -e 'clj/for [i (1 .. 5)]: println(i)' -p
Error: Method nth on class clojure.lang.LongRange$LongChunk not allowed!
wtf? (`-p` prints evaluation value)

Ingy döt Net12:05:20

that's with

+(intern 'ys.clj
+  (clojure.core/with-meta 'for {:macro true})
+  @#'clojure.core/for)

Ingy döt Net13:05:59

diff is now

diff --git a/core/src/yamlscript/runtime.clj b/core/src/yamlscript/runtime.clj
index 52076ba..6d905ce 100644
--- a/core/src/yamlscript/runtime.clj
+++ b/core/src/yamlscript/runtime.clj
@@ -122,7 +122,9 @@
      :classes (classes-map
                 '[clojure.lang.Atom
                   clojure.lang.Fn
+                  clojure.lang.IChunk
                   clojure.lang.Keyword
+                  clojure.lang.LongRange
                   clojure.lang.Range
                   clojure.lang.Seqable
                   clojure.lang.Sequential
diff --git a/core/src/ys/clj.clj b/core/src/ys/clj.clj
index 854f763..703e72a 100644
--- a/core/src/ys/clj.clj
+++ b/core/src/ys/clj.clj
@@ -9,6 +9,9 @@
   (:refer-clojure :only [intern]))
 
 (intern 'ys.clj 'compile   clojure.core/compile)
+(intern 'ys.clj
+  (clojure.core/with-meta 'for {:macro true})
+  @#'clojure.core/for)
 (intern 'ys.clj 'load      clojure.core/load)
 (intern 'ys.clj 'load-file clojure.core/load-file)
 (intern 'ys.clj 'num       clojure.core/num)
diff --git a/core/src/ys/std.clj b/core/src/ys/std.clj
index 27675ca..67a761e 100644
--- a/core/src/ys/std.clj
+++ b/core/src/ys/std.clj
@@ -15,7 +15,8 @@
    [ys.ys :as ys]
    [yamlscript.common :as common]
    [yamlscript.util :as util])
-  (:refer-clojure :exclude [num
+  (:refer-clojure :exclude [for
+                            num
                             print
                             when]))
 
@@ -171,10 +172,9 @@
 (defn dirname [& args]
   (apply util/dirname args))
 
-(defmacro each [bindings & body]
-  `(do
-     (doall (for [~@bindings] (do ~@body)))
-     nil))
+(intern 'ys.std (with-meta 'each {:macro true}) @#'clojure.core/doseq)
+
+(intern 'ys.std (with-meta 'for {:macro true}) @#'clojure.core/doseq)
 
 (defn err [& xs]
   (binding [*out* *err*]

Ingy döt Net13:05:59

getting:

$ YS -e 'for [i (range 1 6)]: println(i)' -p
1
2
3
4
5
nil
$ YS -e 'each [i (range 1 6)]: println(i)' -p
1
2
3
4
5
nil
$ YS -e 'clj/for [i (range 1 6)]: println(i)' -p
Error: Method nth on class clojure.lang.LongRange$LongChunk not allowed!

Ingy döt Net13:05:27

I'll ask in #C015LCR9MHD

phill14:05:57

Well.. Clojure's for is amazingly useful for Generating Stuff (well, generating sequences) from combinations... it comes up all the time in shell scripts and I would expect maybe yaml too... (for [environment ["test" "staging" "north"] service ["ingress" "egress" "digress" "congress"] :when (oblate? environment service)] (str environment "-" service ".log")) and so forth. It would be a shame to use the word to mean something else and then later reinvent it under a third name. True, 'why does my for loop not work [for side effects]?' is a FAQ around here. But I look at it this way... people are bound to hit their head on something on their way in the door. Might as well hit it on for and get it over with. The word Clojure puts around imperatives is (do ...) - it's sometimes silent, as inside defn or when - so doseq is salubrious.

Ingy döt Net14:05:09

I see your point. I actually was trying to make my for be a non-lazy doseq that returned its evaluation like for does but I was unable to.