This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
@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
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)
compare to:
$ ys -ce '
foo:
a > 5: b
a > 10: c
:else: d
'
(foo ((> a 5) b) ((> a 10) c) (:else d))
compare to:
$ ys -ce '
foo %:
a > 5: b
a > 10: c
:else: d
'
(foo (> a 5) b (> a 10) c :else d)
Note the %:
. It gives you cond semantics
but I special case cond
so you don't have to
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
for now some things are hard coded but not for long
...
;; end of mini lesson for today
Thanks much appreciated!
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
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
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!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
Reminder the .
in .*
is ignored. Without it it would be invalid YAML (thus invalid YS)
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. 😞But I expect it will work in the next YS release!!! 🙂
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.
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))))
Nifty! But (slightly off-topic) beware of compiling to concat! https://stuartsierra.com/2015/04/26/clojure-donts-concat
Good to know. Thanks!
Nobody has ever reviewed my Clojure so if you're feeling ambitious 😜
Btw I only use concat when the splat is not in the final position
@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...
The lazy aspect is not necessary. I'll give it a try. Cheers.
You might also be able to help me with a related irritation.
Give me a sec to review it
$ 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.cljI wonder if you can help me turn ys.std/each
into ys.std/for
Is Clojure's doseq
what you're after? https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/doseq
yes, but I really want it to be called for
in YS 😕
I've been trying again. Will post my findings soon
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)
When I replaced doseq
above with for
I got a stack overflow.
No idea why I didn't try doseq this way before... Thanks a lot!!
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
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
Can you not intern a macro?
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)that's with
+(intern 'ys.clj
+ (clojure.core/with-meta 'for {:macro true})
+ @#'clojure.core/for)
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*]
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!
Guessing it is because https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/LongRange.java#L211-L220 is a private class
I'll ask in #C015LCR9MHD
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.
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.