How to bind var's name and value in the clojure macro? - variables

Assum I hava some(more than 20) variables, I want to save them to a file. I don't want to repeat 20 times the same code.
I wrote a macro but it gave me an error.
my test case:
;-----------------------------------------------
(defn processor [ some-parameters ]
(let [
;after some operation ,got these data:
date-str ["JN01","JN02","JN03","JN04"];length 8760
date-temperature (map #(str %2 "," %1) [3.3,4.4,5.5,6.6] date-str) ; all vector's length are 8760
date-ws (map #(str %2 "," %1) [0.2,0.1,0.3,0.4] date-str) ;
;... many variables such like date-relative-humidity,date-pressure, name starts with "date-",
; all same size
]
;(doseq [e date-temperature]
; (println e))
(spit "output-variable_a.TXT"
(with-out-str
(doseq [e date-temperature]
(println e))))
;same 'spit' part will repeat many times
))
(processor 123)
; I NEED to output other variables(ws, wd, relative-humidity, ...)
; Output example:
;JN01,3.3
;JN02,4.4
;JN03,5.5
;JN04,6.6
;-----------------------------------------------
what I want is a macro/function I can use this way:
(write-to-text temperature,ws,wd,pressure,theta-in-k,mixradio)
and this macro/function will do the work.
I don't know how to write such a macro/function.
My macro post here but it doesn't work:
(defmacro write-array [& rest-variables ]
`(doseq [ vname# '~rest-variables ]
;(println vname# vvalue#)
(println "the vname# is" (symbol vname#))
(println "resolve:" (resolve (symbol (str vname# "-lines"))))
(println "resolve2:" (resolve (symbol (str "ws-lines"))))
(let [ vvalue# 5] ;(var-get (resolve (symbol vname#)))]
;----------NOTE: commented out cause '(symbol vname#)' won't work.
;1(spit (str "OUT-" vname# ".TXT" )
;1 (with-out-str
;1 (doseq [ l (var-get (resolve (symbol (str vname# "-lines"))))]
;1 (println l))))
(println vname# vvalue#))))
I found that the problem is (symbol vname#) part, this method only works for a GLOBAL variable, cannot bound to date-temperature in the LET form,(symbol vname#) returns nil.

It looks like you want to write a file of delimited values using binding names and their values from inside a let. Macros transform code during compilation and so they cannot know the run-time values that the symbols you pass are bound to. You can use a macro to emit code that will be evaluated at run-time:
(defmacro to-rows [& args]
(let [names (mapv name args)]
`(cons ~names (map vector ~#args))))
(defn get-stuff []
(let [nums [1 2 3]
chars [\a \b \c]
bools [true false nil]]
(to-rows nums chars bools)))
(get-stuff)
=> (["nums" "chars" "bools"]
[1 \a true]
[2 \b false]
[3 \c nil])
Alternatively you could produce a hash map per row:
(defmacro to-rows [& args]
(let [names (mapv name args)]
`(map (fn [& vs#] (zipmap ~names vs#)) ~#args)))
=> ({"nums" 1, "chars" \a, "bools" true}
{"nums" 2, "chars" \b, "bools" false}
{"nums" 3, "chars" \c, "bools" nil})
You would then need to write that out to a file, either using data.csv or similar code.
To see what to-rows expands to, you can use macroexpand. This is the code being generated at compile-time that will be evaluated at run-time. It does the work of getting the symbol names at compile-time, but emits code that will work on their bound values at run-time.
(macroexpand '(to-rows x y z))
=> (clojure.core/cons ["x" "y" "z"] (clojure.core/map clojure.core/vector x y z))
As an aside, I'm assuming you aren't typing thousands of literal values into let bindings. I think this answers the question as asked but there could likely be a more direct approach than this.

I think you are looking for the function name. To demonstrate:
user=> (defmacro write-columns [& columns]
(let [names (map name columns)]
`(str ~#names)))
#'user/write-columns
user=> (write-columns a b c)
"abc"

You can first capture the variable names and their values into a map:
(defmacro name-map
[& xs]
(let [args-list# (cons 'list (map (juxt (comp keyword str) identity) xs))]
`(into {} ~args-list#)))
If you pass the var names to the macro,
(let [aa 11
bb 22
cc 33]
(name-map aa bb cc))
It gives you a map which you can then use for any further processing:
=> {:aa 11, :bb 22, :cc 33}
(def result *1)
(run!
(fn [[k v]] (println (str "spit file_" (name k) " value: " v)))
result)
=>
spit file_aa value: 11
spit file_bb value: 22
spit file_cc value: 33
Edit: Just noticed it's similar to Taylor's macro. The difference is this one works with primitive types as well, while Taylor's works for the original data (vars resolving to collections).

Related

How to make a sequence of functions in REBOL?

I'd like to collect various functions in a sequence and activate them by the sequence index, like in this simple example:
mul2: func [n] [2 * n]
mul3: func [n] [2 * n]
...
(pick [mul2 mul3] 1) 2 ; yields 2
It seems that mul2 is not treated like a function when it's referred to as a sequence item:
type? (pick [mul2] 1) == word!
Is it possible to arrange functions into sequences?
While experiencing with this example, I noticed that
function? mul2
complains that the argument of mul2 is missing, instead of returning true. Where am I wrong?
mul2 and mul3 are just words
mul2: func [n] [2 * n]
mul3: func [n] [3 * n]
w: pick [mul2 mul3] 1
type? w
; == word!
When you get it's value, then you'll have the function:
f: get pick [mul2 mul3] 1
; == func [n][2 * n]
type? :f
; == function!
function? :f
; == true
Notice that I used :f (get-word) to get the unevaluated value (the function itself) instead of f, because f will be evaluated and will require its parameters.
And then you can use f:
f 6
; == 12

Can't get Clojure macro to execute without expansion error

I'm writing a macro that looks through the metadata on a given symbol and removes any entries that are not keywords, i.e. the key name doesn't start with a ":" e.g.
(meta (var X)) ;; Here's the metadata for testing...
=>
{:line 1,
:column 1,
:file "C:\\Users\\Joe User\\AppData\\Local\\Temp\\form-init11598934441516564808.clj",
:name X,
:ns #object[clojure.lang.Namespace 0x12ed80f6 "thic.core"],
OneHundred 100,
NinetyNine 99}
I want to remove entryes "OneHundred" and "NinetyNine" and leave the rest of the metadata untouched.
So I have a bit of code that works:
(let [Hold# (meta (var X))] ;;Make a copy of the metadata to search.
(map (fn [[kee valu]] ;;Loop through each metadata key/value.
(if
(not= \: (first (str kee))) ;; If we find a non-keyword key,
(reset-meta! (var X) (dissoc (meta (var X)) kee)) ;; remove it from X's metadata.
)
)
Hold# ;;map through this copy of the metadata.
)
)
It works. The entries for "OneHundred" and "NinetyNine" are gone from X's metadata.
Then I code it up into a macro. God bless REPL's.
(defmacro DelMeta! [S]
`(let [Hold# (meta (var ~S))] ;; Hold onto a copy of S's metadata.
(map ;; Scan through the copy looking for keys that DON'T start with ":"
(fn [[kee valu]]
(if ;; If we find metadata whose keyname does not start with a ":"
(not= \: (first (str kee)))
(reset-meta! (var ~S) (dissoc (meta (var ~S)) kee)) ;; remove it from S's metadata.
)
)
Hold# ;; Loop through the copy of S's metadata so as to not confuse things.
)
)
)
Defining the macro with defmacro works without error.
macroexpand-1 on the macro, e.g.
(macroexpand-1 '(DelMeta! X))
expands into the proper code. Here:
(macroexpand-1 '(DelMeta! X))
=>
(clojure.core/let
[Hold__2135__auto__ (clojure.core/meta (var X))]
(clojure.core/map
(clojure.core/fn
[[thic.core/kee thic.core/valu]]
(if
(clojure.core/not= \: (clojure.core/first (clojure.core/str thic.core/kee)))
(clojure.core/reset-meta! (var X) (clojure.core/dissoc (clojure.core/meta (var X)) thic.core/kee))))
Hold__2135__auto__))
BUT!!!
Actually invoking the macro at the REPL with a real parameter blatzes out the most incomprehensible error message:
(DelMeta! X) ;;Invoke DelMeta! macro with symbol X.
Syntax error macroexpanding clojure.core/fn at (C:\Users\Joe User\AppData\Local\Temp\form-init11598934441516564808.clj:1:1).
([thic.core/kee thic.core/valu]) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
(thic.core/kee thic.core/valu) - failed: Extra input at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list
Oh, all-powerful and wise Clojuregods, I beseech thee upon thy mercy.
Whither is my sin?
You don't need a macro here. Also, you are misunderstanding the nature of a Clojure keyword, and the complications of a Clojure Var vs a local variable.
Keep it simple to start by using a local "variable" in a let block instead of a Var:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(dotest
(let [x (with-meta [1 2 3] {:my "meta"})
x2 (vary-meta x assoc :your 25 'abc :def)
x3 (vary-meta x2 dissoc 'abc )]
(is= x [1 2 3])
(is= x2 [1 2 3])
(is= x3 [1 2 3])
(is= (meta x) {:my "meta"})
(is= (meta x2) {:my "meta", :your 25, 'abc :def})
(is= (meta x3) {:my "meta", :your 25}))
So we see the value of x, x2, and x3 is constant. That is the purpose of metadata. The 2nd set of tests shows the effects on the metadata of using vary-meta, which is the best way to change the value.
When we use a Var, it is not only a global value, but it is like a double-indirection of pointers in C. Please see this question:
When to use a Var instead of a function?
This answer also clarifies the difference between a string, a symbol, and a keyword. This is important.
Consider this code
(def ^{:my "meta"} data [1 2 3])
(spyx data)
(spyx-pretty (meta (var data)))
and the result:
data => [1 2 3]
(meta (var data)) =>
{:my "meta",
:line 19,
:column 5,
:file "tst/demo/core.cljc",
:name data,
:ns #object[clojure.lang.Namespace 0x4e4a2bb4 "tst.demo.core"]}
(is= data [1 2 3])
(is= (set (keys (meta (var data))))
#{:my :line :column :file :name :ns})
So we have added the key :my to the metadata as desired. How can we alter it? For a Var, use the function alter-meta!
(alter-meta! (var data) assoc :your 25 'abc :def)
(is= (set (keys (meta (var data))))
#{:ns :name :file 'abc :your :column :line :my})
So we have added 2 new entries to the metadata map. One has the keyword :your as key with value 25, the other has the symbol abc as key with value :def (a keyword).
We can also use alter-meta! to remote a key/val pair from the metadata map:
(alter-meta! (var data) dissoc 'abc )
(is= (set (keys (meta (var data))))
#{:ns :name :file :your :column :line :my})
Keyword vs Symbol vs String
A string literal in a source file has double quotes at each end, but they are not characters in the string. Similarly a keyword literal in a source file needs a leading colon to identify it as such. However, neither the double-quotes of the string nor the colon of the keyword are a part of the name of that value.
Thus, you can't identify a keyword by the colon. You should use these functions to identify different data types:
string?
keyword?
symbol?
the above are from the Clojure CheatSheet. So, the code you really want is:
(defn remove-metadata-symbol-keys
[var-obj]
(assert (var? var-obj)) ; verify it is a Var
(doseq [k (keys (meta var-obj))]
(when (not (keyword? k))
(alter-meta! var-obj dissoc k))))
with a sample:
(def ^{:some "stuff" 'other :things} myVar [1 2 3])
(newline) (spyx-pretty (meta (var myVar)))
(remove-metadata-symbol-keys (var myVar))
(newline) (spyx-pretty (meta (var myVar)))
and result:
(meta (var myVar)) =>
{:some "stuff",
other :things, ; *** to be removed ***
:line 42,
:column 5,
:file "tst/demo/core.cljc",
:name myVar,
:ns #object[clojure.lang.Namespace 0x9b9155f "tst.demo.core"]}
(meta (var myVar)) => ; *** after removing non-keyword keys ***
{:some "stuff",
:line 42,
:column 5,
:file "tst/demo/core.cljc",
:name myVar,
:ns #object[clojure.lang.Namespace 0x9b9155f "tst.demo.core"]}
The above code was all run using this template project.

Split lines in clojure while reading from file

I am learning clojure at school and I have an exam coming up. I was just working on a few things to make sure I get the hang of it.
I am trying to read from a file line by line and as I do, I want to split the line whenever there is a ";".
Here is my code so far
(defn readFile []
(map (fn [line] (clojure.string/split line #";"))
(with-open [rdr (reader "C:/Users/Rohil/Documents/work.txt.txt")]
(doseq [line (line-seq rdr)]
(clojure.string/split line #";")
(println line)))))
When I do this, I still get the output:
"I;Am;A;String;"
Am I missing something?
I'm not sure if you need this at school, but since Gary already gave an excellent answer, consider this as a bonus.
You can do elegant transformations on lines of text with transducers. The ingredient you need is something that allows you to treat the lines as a reducible collection and which closes the reader when you're done reducing:
(defn lines-reducible [^BufferedReader rdr]
(reify clojure.lang.IReduceInit
(reduce [this f init]
(try
(loop [state init]
(if (reduced? state)
#state
(if-let [line (.readLine rdr)]
(recur (f state line))
state)))
(finally
(.close rdr))))))
Now you're able to do the following, given input work.txt:
I;am;a;string
Next;line;please
Count the length of each 'split'
(require '[clojure.string :as str])
(require '[clojure.java.io :as io])
(into []
(comp
(mapcat #(str/split % #";"))
(map count))
(lines-reducible (io/reader "/tmp/work.txt")))
;;=> [1 2 1 6 4 4 6]
Sum the length of all 'splits'
(transduce
(comp
(mapcat #(str/split % #";"))
(map count))
+
(lines-reducible (io/reader "/tmp/work.txt")))
;;=> 24
Sum the length of all words until we find a word that is longer than 5
(transduce
(comp
(mapcat #(str/split % #";"))
(map count))
(fn
([] 0)
([sum] sum)
([sum l]
(if (> l 5)
(reduced sum)
(+ sum l))))
(lines-reducible (io/reader "/tmp/work.txt")))
or with take-while:
(transduce
(comp
(mapcat #(str/split % #";"))
(map count)
(take-while #(> 5 %)))
+
(lines-reducible (io/reader "/tmp/work.txt")))
Read https://tech.grammarly.com/blog/building-etl-pipelines-with-clojure for more details.
TL;DR embrace the REPL and embrace immutability
Your question was "what am I missing?" and to that I'd say you're missing one of the best features of Clojure, the REPL.
Edit: you might also be missing that Clojure uses immutable data structures so
consider this code snippet:
(doseq [x [1 2 3]]
(inc x)
(prn x))
This code does not print "2 3 4"
it prints "1 2 3" because x isn't a mutable variable.
During the first iteration (inc x) gets called, returns 2, and that gets thrown away because it wasn't passed to anything, then (prn x) prints the value of x which is still 1.
Now consider this code snippet:
(doseq [x [1 2 3]] (prn (inc x)))
During the first iteration the inc passes its return value to prn so you get 2
Long example:
I don't want to rob you of the opportunity to solve the problem yourself so I'll use a different problem as an example.
Given the file "birds.txt"
with the data "1chicken\n 2duck\n 3Larry"
you want to write a function that takes a file and returns a sequence of bird names
Lets break this problem down into smaller chunks:
first lets read the file and split it up into lines
(slurp "birds.txt") will give us the whole file a string
clojure.string/split-lines will give us a collection with each line as an element in the collection
(clojure.string/split-lines (slurp "birds.txt")) gets us ["1chicken" "2duck" "3Larry"]
At this point we could map some function over that collection to strip out the number like (map #(clojure.string/replace % #"\d" "") birds-collection)
or we could just move that step up the pipeline when the whole file is one string.
Now that we have all of our pieces we can put them together in a functional pipeline where the result of one piece feeds into the next
In Clojure there is a nice macro to make this more readable, the -> macro
It takes the result of one computation and injects it as the first argument to the next
so our pipeline looks like this:
(-> "C:/birds.txt"
slurp
(clojure.string/replace #"\d" "")
clojure.string/split-lines)
last note on style, for Clojure functions you want to stick to kebab case so readFile should be read-file
I would keep it simple, and code it like this:
(ns tst.demo.core
(:use tupelo.test)
(:require [tupelo.core :as t]
[clojure.string :as str] ))
(def text
"I;am;a;line;
This;is;another;one
Followed;by;this;")
(def tmp-file-name "/tmp/lines.txt")
(dotest
(spit tmp-file-name text) ; write it to a tmp file
(let [lines (str/split-lines (slurp tmp-file-name))
result (for [line lines]
(for [word (str/split line #";")]
(str/trim word)))
result-flat (flatten result)]
(is= result
[["I" "am" "a" "line"]
["This" "is" "another" "one"]
["Followed" "by" "this"]])
Notice that result is a doubly-nested (2D) matrix of words. The simplest way to undo this is the flatten function to produce result-flat:
(is= result-flat
["I" "am" "a" "line" "This" "is" "another" "one" "Followed" "by" "this"])))
You could also use apply concat as in:
(is= (apply concat result) result-flat)
If you want to avoid building up a 2D matrix in the first place, you can use a generator function (a la Python) via lazy-gen and yield from the Tupelo library:
(dotest
(spit tmp-file-name text) ; write it to a tmp file
(let [lines (str/split-lines (slurp tmp-file-name))
result (t/lazy-gen
(doseq [line lines]
(let [words (str/split line #";")]
(doseq [word words]
(t/yield (str/trim word))))))]
(is= result
["I" "am" "a" "line" "This" "is" "another" "one" "Followed" "by" "this"])))
In this case, lazy-gen creates the generator function.
Notice that for has been replaced with doseq, and the yield function places each word into the output lazy sequence.

Can you create local variables without a `let`?

eg1 - using a let
(defun demo1 ()
(let ((a 1)
(b 2))
; these print fine
(print a)
(print b)))
(demo1)
; these get "no value" errors, as expected
(print a)
(print b)
output:
1
2 *** - EVAL: variable A has no value
eg2 - without a let, the variables escape
(this is very surprising for anyone used to more modern scope-rules like eg ruby's)
(defun demo2 ()
(setf a 1)
(setf b 2)
; these print fine
(print a)
(print b))
(demo2)
; these escape, and also print with no error!
(print a)
(print b)
output:
1
2
1
2
how can you make them not escape?
i monkey'd around with setq and defvar
(which were mentioned in the only results i could find by looking for documentation on "local variables")
but no joy at all
eg3 - trying to use a macro
(
this is the practical problem i wanted to solve in the first place --
the syntax of let forces you to use extra layers of parens,
and wrap up the entire function body in the outermost layer,
which just makes it harder to read and write for no reason
(since the overwhelmingly most common use case for let
always includes the entire function body and nothing else),
so i wanted to make a with macro instead
)
(defmacro with (&rest list-of-pairs)
(loop for (name value) in list-of-pairs
do `(setf ,name ,value) ;A
; do (setf name value) ;B
; (i tried both A and B)
))
(defun demo3 ()
(with (a 1)
(b 2))
; this gets a "no value" error when called
(print a)
(print b))
(demo3)
; it never even gets to this point cuz of the above error
(print a)
(print b)
output:
*** - PROGN: variable A has no value
how can you get the variables to escape into the function scope and not beyond?
[
this question asks
can anyone tell me how to define a local variable in lisp other than let?
but none of the answers were helpful to me
]
EDIT-TO-ADD eg4
thinking about the way the loop macro works
(from the point of view of someone calling it without understanding its internals, i mean)...
well, look:
(loop for i from 1 to 5
do (print i))
i don't know yet what the definition of loop looks like,
but it's abstractly something like this, right?:
(defmacro loop ([args somehow,
including the `sexpr`
which goes after the `do` keyword in the macro call])
[other stuff]
do ([sexpr])
[other stuff])
(i'm focusing on the do keyword as an example just because the syntax of the call is relatively simple.)
so what i actually need to do is make my own my-defun macro
and include a with keyword,
right?
something like this:
(defmacro my-defun ([args somehow,
including a `paired-list-of-names-and-values`
to go after a `with` keyword in the macro call])
(let
([paired-list-of-names-and-values])
([function body])))
(my-defun demo4 ()
with (
(a 1)
(b 2)
)
; this should print
(print a)
(print b))
(demo4)
; this should get a "no value" error
(print a)
(print b)
am i on the right track here?
if so, where do i go from here?
like, what are some simple, straight-forward macro definitions i can look at to induce how they work?
or something like that
Simple rule: SETF or SETQ do not create variables. Neither local nor global. They just set variables.
Never set an undefined variable using SETQ and SETF. This is Common Lisp, not Ruby.
It also does not help to create SETF forms with a macro. Why should this make a difference?
Defining local variables
If you look into the Common Lisp standard, there are a zillion constructs which allow to define local variables: DEFUN, DEFMETHOD, LET, LET*, DO, DOTIMES, FLET, LABELS, LAMBDA, DESTRUCTURING-BIND, MULTIPLE-VALUE-BIND, ...
Local variables in functions
If you look at functions, the argument list for functions allows you to define local variables:
required argument
optional argument
keyword argument
auxilliary argument
rest argument
You can define functions with LAMBDA, DEFUN, ...
Example for &optional:
((lambda (a &optional (b 20))
... ; a and b are known here inside the function
)
10) ; we don't need to pass an arg for `b`, optional!
Example for &aux:
((lambda (a &aux (b (+ a 20)))
... ; a and b are known here inside the function
)
10) ; we CAN't pass an arg for `b`, auxiliary!
Variables in LOOP
You don't need to guess what the LOOP macro does, you can ask Lisp to show you - here using LispWorks:
CL-USER 27 > (pprint (macroexpand '(loop for i from 1 to 5
do (print i))))
(BLOCK NIL
(MACROLET ((LOOP-FINISH () '(GO #:|end-loop-82961|)))
(LET ((I 1)
(#:|to-82964| 5)
(#:|by-82965| 1))
(TAGBODY (PROGN (SYSTEM::INTERNAL-IF (OR (> I #:|to-82964|))
(GO #:|end-loop-82961|)))
#:|begin-loop-82960|
NIL
(PRINT I)
(PROGN
(LET ((#:|temp-82966| (+ I #:|by-82965|)))
(SETQ I #:|temp-82966|))
(SYSTEM::INTERNAL-IF (OR (> I #:|to-82964|))
(GO #:|end-loop-82961|)))
(GO #:|begin-loop-82960|)
#:|end-loop-82961|
(RETURN-FROM NIL NIL)))))
As you can see it expands into a form, where the variable i is introduced by a LET. It also expands into a form, which makes use of a goto construct.
As you can see, you can implement a very fancy syntax - like the LOOP syntax - in Lisp. But the implementation of LOOP is large and non-trivial. Nothing for a beginner.
I happen to like how bindings are introduced in Lisp, and I miss it in other languages.
However, I do not attempt to change them to my liking.
Instead, I follow the particular idioms of other languages.
In a comment, you ask:
are you saying that I should be able to use the info you game me to do that, or are you saying that it's just not possible?
In fact, Rainer Joswig is telling you that you should not do that, for the same reason that you do not introduce BEGIN and END macros in C code to replace braces: just work with the existing language instead of directly trying to "fix" it.
By the way, please understand that the existing design of LET is not an accident, and work as expected. The fact that is looks weird to you "from a modern perspective" (see OCaml/F#) does not imply that it is wrong or badly designed. I don't know what you mean with the following comment:
it can properly shadow and unshadow variable definition for parameters passed in to a function
... but I can tell you that it does not make much sense as-is.
Please consult Online Tutorials for programming Common Lisp to have a better view of Lisp before trying to modify it.
Rainer did cover this above, but perhaps not addressed specifically to your example: the &optional parameter for function definition (defun or lambda) seems to be exactly what you are looking for (and indeed is implemented as a macro around let, I believe).
(defun do-stuff (&optional (a 10) (b 20))
(print a)
(print b))
(do-stuff)
10
20
20 <-- return value as last form evaluated in defun
(print a) --> ERROR; it has not escaped.
If you just wanted to try implementing the macro, good on you!
[EDIT:
i just realized that i messed up on the arguement handling,
so my "solution" is broken
if there aren't at least three lists given to my-defun after its parameter list.
i put the (i think) actual working solution at the end.
]
i actually figured this out!
it's not too hard at all,
even for a newb.
(
i mean,
i wouldn't be surprised if horrible things would happen if one actually tried to use it in "real code",
because edge-cases or something,
but it's a working proof-of-concept
)
anyway, here's a definition of the my-defun macro,
working as i described in eg4 in my question:
(
please nobody edit the weird formatting --
i realize it's non-standard,
but it really helps newbs read difficult new stuff.
i mean,
if another newb like me ever reads this,
i think it'll help them significantly.
)
(defmacro my-defun (name params withsymbol withlist &body body)
(cond
((equal withsymbol 'with)
; (print 'withsymbol_set_to_) ;diagnostic
; (print withsymbol) ;diagnostic
; (print 'withlist_set_to_) ;diagnostic
; (print withlist) ;diagnostic
; (print ;diagnostic
`(defun ,name ,params
(let ,withlist
(progn ,#body)
)
)
; ) ;diagnostic
)
(t
; (print 'withsymbol_not_set_to_with_but_) ;diagnostic
; (print withsymbol) ;diagnostic
; (print 'withlist_set_to_) ;diagnostic
; (print withlist) ;diagnostic
; (print ;diagnostic
`(defun ,name ,params
(progn ,withsymbol ,withlist ,#body)
)
; ) ;diagnostic
)
)
)
first test, with a with:
(my-defun demo4 (x)
with (
(a 1)
(b 2)
)
; this prints!
(print a)
(print b)
(print x)
)
(demo4 "hi")
; this correctly gets a "no value" error!
(print a)
(print b)
(print x)
output:
1
2
"hi"
output with diagnostic lines uncommented:
WITHSYMBOL_SET_TO_
WITH
WITHLIST_SET_TO_
((A 1) (B 2))
(DEFUN DEMO4 (X) (LET ((A 1) (B 2)) (PROGN (PRINT A) (PRINT B) (PRINT X))))
1
2
"hi"
second test, with no with:
(so it acts exactly like a normal defun)
(my-defun demo4 (x)
; (this stuff also prints)
(print "i am not the withsymbol")
(print "this is not the withlist")
; this prints!
(print "symbol 'a would have no value")
(print "symbol 'b would have no value")
(print x)
)
(demo4 "hi")
; this correctly gets a "no value" error!
'(print a)
'(print b)
'(print x)
output:
"i am not the withsymbol"
"this is not the withlist"
"symbol 'a would have no value"
"symbol 'b would have no value"
"hi"
output with diagnostic lines uncommented:
WITHSYMBOL_NOT_SET_TO_WITH_BUT_
(PRINT "i am not the withsymbol")
WITHLIST_SET_TO_
(PRINT "this is not the withlist")
(DEFUN DEMO4 (X)
(PROGN (PRINT "i am not the withsymbol") (PRINT "this is not the withlist") (PRINT "symbol 'a would have no value")
(PRINT "symbol 'b would have no value") (PRINT X)))
"i am not the withsymbol"
"this is not the withlist"
"symbol 'a would have no value"
"symbol 'b would have no value"
"hi"
minimally different examples:
using defun with let
and using my-defun with with
(just wanted to eyeball to what extent the result looks worth the trouble xD)
( defun demo (x)
(let (
(a 1)
(b 2)
)
(print a)
(print b)
(print x)
)
)
(my-defun demo (x)
with (
(a 1)
(b 2)
)
(print a)
(print b)
(print x)
)
ACTUALLY WORKING SOLUTION (I HOPE):
(defmacro fun (name params &rest rest)
(let (
(withsymbol (car rest))
(withlist (car (cdr rest)))
(body (cdr (cdr rest)))
)
; (p withsymbol ) ;;debug
; (p withlist ) ;;debug
; (p body ) ;;debug
(cond
((equal withsymbol 'with)
; (print 'BRANCH_A) ;;debug
; (print ;;debug
`(defun ,name ,params
(let* ,withlist
(progn ,#body)
)
)
; ) ;;debug
)
(t
; (print 'BRANCH_B) ;;debug
; (print ;;debug
`(defun ,name ,params
(progn ,#rest)
)
; ) ;;debug
)
)
)
)
;; for debugging
(defmacro p (symbol)
`(format t "~A ~A~%" ',symbol ,symbol)
)
although that was the earliest working version of the code,
so maybe i messed it up without noticing by renaming variables incompletely or something.
the most recent code that i actually just tested is more complicated:
;; used in debug
(defmacro p (symbol)
`(format t "~A ~A~%" ',symbol ,symbol))
(defmacro mac-or-fun (which-one name params rest)
(let (
(withsymbol (car rest))
(withlist (car (cdr rest)))
(body (cdr (cdr rest)))
)
; (p withsymbol ) ;;debug
; (p withlist ) ;;debug
; (p body ) ;;debug
(cond
((equal withsymbol 'with)
; (print 'BRANCH_A) ;;debug
; (print ;;debug
`(,which-one ,name ,params
(let* ,withlist
(progn ,#body)
)
)
; ) ;;debug
)
((equal withsymbol 'omwith)
; (print 'BRANCH_A) ;;debug
; (print ;;debug
`(,which-one ,name ,params
(omlet ,withlist
(progn ,#body)
)
)
; ) ;;debug
)
(t
; (print 'BRANCH_B) ;;debug
; (print ;;debug
`(,which-one ,name ,params
(progn ,#rest)
)
; ) ;;debug
)
)
)
)
(defmacro fun (name params &rest rest)
`(mac-or-fun defun ,name ,params ,rest))
(defmacro mac (name params &rest rest)
`(mac-or-fun defmacro ,name ,params ,rest))
;; for use in tests
(defun ps (&rest stringlist)
(format t "~A~%" (eval `(concatenate 'string ,#stringlist))))
(defparameter *vs-use-count* 0)
(defmacro vs (&rest title)
(setf *vs-use-count* (+ 1 *vs-use-count*))
(ps "
SECTION " (write-to-string *vs-use-count*) " " (write-to-string title) " -"
)
)
;;;tests
(progn
(vs fun works with "with")
(fun f ()
with ((a 1))
(print a)
)
(f)
(vs fun works with "nil")
(fun f ()
()
)
(print(f))
(vs original fun test with "with")
(fun demo4 (x)
with (
(a 1)
(b 2)
)
; this prints!
(print a)
(print b)
(print x)
)
(demo4 "hi")
; these would correctly gets a "no value" error!
'(print a)
'(print b)
'(print x)
(vs original fun test with no "with")
(fun demo4 (x)
; (this stuff also prints)
(print "i am not the withsymbol")
(print "this is not the withlist")
; this prints!
(print "symbol 'a would have no value")
(print "symbol 'b would have no value")
(print x)
)
(demo4 "hi")
; these would correctly gets a "no value" error!
'(print a)
'(print b)
'(print x)
(vs mac works with "with")
(mac m ()
with ((a 1))
(print a)
)
(m)
(vs mac works with "nil")
(mac m ()
()
)
(print(m))
)
;;; more stuff,
;;; leading up to the macro `omlet`,
;;; which is used in `mac-or-fun`
(fun pair-up (l)
with (
(a (car l) )
(b (car (cdr l)) )
(l-past-b (cdr (cdr l)) )
)
(cond
(
(equal 2 (length l))
(list l)
)
(t
(cons (list a b) (pair-up l-past-b))
)
)
)
(fun crack-1 (eggs)
with (
(paired-list (pair-up eggs))
(paired-list (loop for (k v) in paired-list collect `(,k ',v)))
)
paired-list
)
(fun crack-2 (eggs)
with (
(key-name (car eggs))
(value-name (car (cdr eggs)))
(eggs (cdr (cdr eggs)))
(paired-list (pair-up eggs))
(keys-list (loop for pair in paired-list collect (first pair)))
(values-list (loop for pair in paired-list collect (second pair)))
(key-name-keys (list key-name keys-list))
(value-name-values (list value-name values-list))
(paired-list (append paired-list (list key-name-keys value-name-values)))
(paired-list (loop for (k v) in paired-list collect `(,k ',v)))
)
paired-list
)
(fun crack (eggs)
(if
(and
(equal '- (car eggs))
(oddp (length eggs))
)
(crack-2 (cdr eggs))
(crack-1 eggs)
)
)
(mac omlet (eggs &body body)
with ((to-let (crack eggs)))
`(let ,to-let (progn ,#body))
)
(mac lemego (&rest eggs)
with ((to-set (crack eggs)))
(loop for (name value) in to-set
do (eval `(setf ,name ,value))
)
)
;;; more tests
(progn
(vs omlet 1)
(omlet (
x a
y b
z c
)
(print x )
(print y )
(print z )
)
(vs omlet 2)
(omlet (
- names digits
one 1
two 2
three 3
)
(print one )
(print two )
(print three )
(print names )
(print digits )
)
(vs fun with omwith 1)
(fun f ()
omwith (
x a
y b
z c
)
(print x )
(print y )
(print z )
)
(f)
(vs fun with omwith 2)
(fun f ()
omwith (
- names digits
one 1
two 2
three 3
)
(print one )
(print two )
(print three )
(print names )
(print digits )
)
(f)
(vs lemego 1)
(lemego
x a
y b
z c
)
(print x )
(print y )
(print z )
(vs lemego 2)
(lemego
- names digits
one 1
two 2
three 3
)
(print one )
(print two )
(print three )
(print names )
(print digits )
)

Scheme Help - File Statistics

So I have to finish a project in Scheme and I'm pretty stuck. Basically, what the program does is open a file and output the statistics. Right now I am able to count the number of characters, but I also need to count the number of lines and words. I'm just trying to tackle this situation for now but eventually I also have to take in two files - the first being a text file, like a book. The second will be a list of words, I have to count how many times those words appear in the first file. Obviously I'll have to work with lists but I would love some help on where to being. Here is the code that I have so far (and works)
(define filestats
(lambda (srcf wordcount linecount charcount )
(if (eof-object? (peek-char srcf ) )
(begin
(close-port srcf)
(display linecount)
(display " ")
(display wordcount)
(display " ")
(display charcount)
(newline) ()
)
(begin
(read-char srcf)
(filestats srcf 0 0 (+ charcount 1))
)
)
)
)
(define filestatistics
(lambda (src)
(let ((file (open-input-file src)))
(filestats file 0 0 0)
)
)
)
How about 'tokenizing' the file into a list of lines, where a line is a list of words, and a word is a list of characters.
(define (tokenize file)
(with-input-from-file file
(lambda ()
(let reading ((lines '()) (words '()) (chars '()))
(let ((char (read-char)))
(if (eof-object? char)
(reverse lines)
(case char
((#\newline) (reading (cons (reverse (cons (reverse chars) words)) lines) '() '()))
((#\space) (reading lines (cons (reverse chars) words) '()))
(else (reading lines words (cons char chars))))))))))
once you've done this, the rest is trivial.
> (tokenize "foo.data")
(((#\a #\b #\c) (#\d #\e #\f))
((#\1 #\2 #\3) (#\x #\y #\z)))
The word count algorithm using Scheme has been explained before in Stack Overflow, for example in here (scroll up to the top of the page to see an equivalent program in C):
(define (word-count input-port)
(let loop ((c (read-char input-port))
(nl 0)
(nw 0)
(nc 0)
(state 'out))
(cond ((eof-object? c)
(printf "nl: ~s, nw: ~s, nc: ~s\n" nl nw nc))
((char=? c #\newline)
(loop (read-char input-port) (add1 nl) nw (add1 nc) 'out))
((char-whitespace? c)
(loop (read-char input-port) nl nw (add1 nc) 'out))
((eq? state 'out)
(loop (read-char input-port) nl (add1 nw) (add1 nc) 'in))
(else
(loop (read-char input-port) nl nw (add1 nc) state)))))
The procedure receives an input port as a parameter, so it's possible to apply it to, say, a file. Notice that for counting words and lines you'll need to test if the current char is either a new line character or a white space character. And an extra flag (called state in the code) is needed for keeping track of the start/end of a new word.