Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

time-it macro #39

Open
pyx opened this issue Dec 22, 2016 · 5 comments
Open

time-it macro #39

pyx opened this issue Dec 22, 2016 · 5 comments
Labels
feature New feature or request

Comments

@pyx
Copy link

pyx commented Dec 22, 2016

As inspired by @gilch in discussion here: hylang/hy#1179

The macro:

(defmacro/g! time-it [expr &optional setup round]
  `((fn []
      (do (import [timeit [timeit :as ~g!timeit]])
      ~(when setup setup)
      (defn testee [] ~expr)
      (setv kwargs {})
      ~(when round `(assoc kwargs "number" ~round))
      (apply ~g!timeit [testee] kwargs)))))
hy 0.11.0+353.gca6fd66 using CPython(default) 3.5.2 on Linux
=> (defmacro/g! time-it [expr &optional setup round]
...   `((fn []
...       (do (import [timeit [timeit :as ~g!timeit]])
...       ~(when setup setup)
...       (defn testee [] ~expr)
...       (setv kwargs {})
...       ~(when round `(assoc kwargs "number" ~round))
...       (apply ~g!timeit [testee] kwargs)))))
=> (time-it (inc 1))
0.6166748020004889
=> (time-it (inc anwser))
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 1, in _hy_anon_fn_2
  File "/usr/lib64/python3.5/timeit.py", line 213, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
  File "/usr/lib64/python3.5/timeit.py", line 178, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
  File "<input>", line 1, in testee
NameError: name 'anwser' is not defined
=> (time-it (inc answer) (do (print "thinking.." (setv answer 42))))
thinking.. 42
0.641465346001496
=> (time-it (inc a) (setv a 12) 100)
6.975599899305962e-05
=> (time-it (inc 1) () 100)
6.572400161530823e-05
=> (time-it (inc 1) :round 100)  ;; works by accident, :round by itself is a valid expression
6.490799933089875e-05

Another approach is using read-str

Caveat

I've been fighting the macro system for the last hour, as we cannot use &kwargs with defmacro, so unless we use &rest and parse the argument ourselves (I have a kind-of-working prototype, but it is so ugly that I do not dare to share), the form of the macro is a bit rigid, I skipped the timer argument for simplicity's sake, and right now, one have to pass in the setup code to specify the round of iteration, see above.

@gilch
Copy link
Member

gilch commented Dec 22, 2016

For maximum flexibility, a macro has to be able to interpret a keyword as as keyword instead of a kwarg pair. This way you can write macros that contain keywords as part of the syntax, even if there are two in a row or one at the end. I think you could simply have the macro call a function that accepts &kwargs. No need to parse it yourself.

@pyx
Copy link
Author

pyx commented Dec 22, 2016

Revise, remove excessive do

(defmacro/g! time-it [expr &optional setup round]
  `((fn []
      (import [timeit [timeit :as ~g!timeit]])
      ~(when setup setup)
      (defn testee [] ~expr)
      (setv kwargs {})
      ~(when round `(assoc kwargs "number" ~round))
      (apply ~g!timeit [testee] kwargs))))

In the beginning, I did not wrap everything inside a function, thus the do form, when I switched to function (to have local scope), I forgot to remove the do

@pyx
Copy link
Author

pyx commented Dec 22, 2016

@gilch
Ah, that's a nice idea, a helper function will do, indeed. Thanks.
I will come back to this when I have time, I need some fresh air now.

@gilch
Copy link
Member

gilch commented Dec 22, 2016

A simple example:

=> (defmacro foo [x &rest xs] `((fn [s &kwargs kws] (, s kws)) '~x ~@xs))
=> (foo norf :bar 1 :baz 2)
('norf', {'bar': 1, 'baz': 2})

Note that the macro can quote the first symbol and still parse the remaining kwargs.

@pyx
Copy link
Author

pyx commented Dec 22, 2016

Okay, this is what I have so far

(defmacro/g! time-it [expr &rest options]
  (if
    (not (keyword? (first options)))
      (setv setup (first options) args (rest options))
    (in :setup options)
      (do
        (setv index (.index options (keyword "setup")))
        (when (= index (dec (len options)))
          (macro-error None "Keyword argument :setup needs a value."))
        (setv skipped (, index (inc index))
              setup (get options (inc index))
              args (list-comp exp
                              [(, i exp) (enumerate options)]
                              (not-in i skipped))))
    (setv setup None args options))
  `((fn []
      (import [timeit [timeit :as ~g!timeit]])
      ~(when setup setup)
      (~g!timeit (fn [] ~expr) "pass" ~@args))))

Test drive:

hy 0.11.0+353.gca6fd66 using CPython(default) 3.5.2 on Linux
=> (defmacro/g! time-it [expr &rest options]
...   (if
...     (not (keyword? (first options)))
...       (setv setup (first options) args (rest options))
...     (in :setup options)
...       (do
...         (setv index (.index options (keyword "setup")))
...         (when (= index (dec (len options)))
...           (macro-error None "Keyword argument :setup needs a value."))
...         (setv skipped (, index (inc index))
...               setup (get options (inc index))
...               args (list-comp exp
...                               [(, i exp) (enumerate options)]
...                               (not-in i skipped))))
...     (setv setup None args options))
...   `((fn []
...       (import [timeit [timeit :as ~g!timeit]])
...       ~(when setup setup)
...       (~g!timeit (fn [] ~expr) "pass" ~@args))))

=> (time-it (inc 1))
0.6054470939998282

=> (time-it (inc answer))
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 1, in _hy_anon_fn_2
  File "/usr/lib64/python3.5/timeit.py", line 213, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
  File "/usr/lib64/python3.5/timeit.py", line 178, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
  File "<input>", line 1, in _hy_anon_fn_1
NameError: name 'answer' is not defined

=> (time-it (inc answer) (setv answer 42))
0.6117291800001112

=> answer
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'answer' is not defined

=> (time-it (print 'Hit) :number 3)
Hit
Hit
Hit
0.00010172399925068021

=> (time-it (print 'Hit) () :number 3)
Hit
Hit
Hit
9.308399967267178e-05

=> (time-it (print 'Hit) :number 3 :setup (print 'Setup...))
Setup...
Hit
Hit
Hit
7.982400347827934e-05

=> (time-it (print 'Hit) :setup (print 'Setup...) :number 3)
Setup...
Hit
Hit
Hit
8.413199975620955e-05

=> (time-it (inc 1) :setup (print 'Setup...))
Setup...
0.5945940750025329

=> (time-it (inc 1) :setup)
  File "<input>", line 1, column 1

  (time-it (inc 1) :setup)
  ^----------------------^
HyMacroExpansionError: b'Keyword argument :setup needs a value.'

=> (time-it (inc 1) :i-dont-know-what-i-am-doing 42)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 1, in _hy_anon_fn_2
TypeError: timeit() got an unexpected keyword argument 'i_dont_know_what_i_am_doing'

@Kodiologist Kodiologist added the feature New feature or request label Jan 4, 2017
@Kodiologist Kodiologist transferred this issue from hylang/hy Apr 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants