Skip to content

Commit

Permalink
Merge pull request #101 from Kodiologist/oop
Browse files Browse the repository at this point in the history
Add `meth` and `ameth`
  • Loading branch information
Kodiologist authored Sep 21, 2024
2 parents 25a96be + 227d607 commit ccb9a48
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python: [3.8, 3.9, '3.10', 3.11, 3.12, pypy-3.9]
python: [3.8, 3.9, '3.10', 3.11, 3.12, 3.13-dev, pypy-3.10]

name: ${{ matrix.python }}
runs-on: ubuntu-latest
Expand Down
7 changes: 7 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
.. default-role:: code

Unreleased
======================================================

New Features
------------------------------
* New macros `meth` and `ameth`.

0.6.0 (released 2024-05-20; uses Hy 0.29.*)
======================================================

Expand Down
7 changes: 7 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ API
.. hy:autofunction:: match-fn-params
.. hy:automacro:: with-gensyms
``oop`` — Tools for object-oriented programming
----------------------------------------------------------------------
.. hy:automodule:: hyrule.oop
.. hy:automacro:: meth
.. hy:automacro:: ameth
``pprint`` — Pretty-printing data structures
----------------------------------------------------------------------
.. hy:automodule:: hyrule.hypprint
Expand Down
2 changes: 1 addition & 1 deletion hyrule/argmove.hy
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ also known as arrow macros."
(defmacro doto [form #* expressions]

"As :hy:func:`->`, but instead of the return value of each expression being
passed to the next, a single object (obtained by evaluating the orignial
passed to the next, a single object (obtained by evaluating the original
first argument ``form``) is used every time. In other words, ::
(doto x (foo a b) (bar c d))
Expand Down
1 change: 1 addition & 0 deletions hyrule/hy_init.hy
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
hyrule.destructure *
hyrule.macrotools * :readers *
hyrule.misc *
hyrule.oop *
hyrule.sequences *)
(import
hyrule.collections *
Expand Down
66 changes: 66 additions & 0 deletions hyrule/oop.hy
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
(import
hyrule.macrotools [map-model])


(defmacro meth [#* args]
#[[A replacement for :hy:func:`defn` that provides syntactic sugar for ``self``. As the name suggests, it's most useful for defining methods. The parameter list is automatically prepended with ``self``, and any reference to a symbol beginning with ``@``, such as ``@foo``, is replaced by ``self.foo``::
(defclass BirdWatcher []
(meth observe [bird]
(@log bird)
(setv @last-seen bird)
@last-seen)
(meth log [bird]
(print "I just saw:" bird)))
(setv x (BirdWatcher))
(.observe x "sparrow") ; I just saw: sparrow
(.observe x "cardinal") ; I just saw: cardinal
(print x.last-seen) ; cardinal
``@``-symbols that appear in the lambda list of the method are special: ``@foo`` is replaced with simply ``foo``, and the method body is prepended with ``(setv self.foo foo)``. This is convenient for parameters to ``__init__`` that set attributes of the same name::
(defclass Rectangle []
(meth __init__ [@width @height])
; Look Ma, no body!
(meth area []
(* @width @height)))
(setv x (Rectangle 3 4))
(print (.area x)) ; => 12
The symbol ``@,`` is replaced with just plain ``self``. By contrast, the symbol ``@`` is left untouched, since it may refer to the Hy core macro :hy:func:`@ <hy.pyops.@>`.]]

(if (and args (isinstance (get args 0) hy.models.List))
(setv [decorators name params #* body] args)
(setv decorators [] [name params #* body] args))
`(defn ~decorators ~name ~@(_meth params body)))

(defmacro ameth [params #* body]
"Define an anonymous method. ``ameth`` is to :hy:func:`meth` as :hy:func:`fn` is to :hy:func:`defn`: it has the same syntax except that no method name (or decorators) are allowed."
`(fn ~@(_meth params body)))


(defn _meth [params body]
(setv to-set [])
(setv params (map-model params (fn [x]
(when (and (isinstance x hy.models.Symbol) (.startswith x "@"))
(setv x (hy.models.Symbol (cut x 1 None)))
(.append to-set x)
x))))
(setv body (map-model body (fn [x]
(when (and (isinstance x hy.models.Symbol) (.startswith x "@"))
(cond
(= x '@) '@
(= x '@,) 'self
True `(. self ~(hy.models.Symbol (cut x 1 None))))))))
`[
[self ~@params]
~@(gfor
sym to-set
`(setv (. self ~sym) ~sym))
~@body])
109 changes: 109 additions & 0 deletions tests/test_oop.hy
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
(require
hyrule [meth ameth])
(import
pytest)


(defn example-decorator [f]
(setv f.da "hello")
f)


(defn test-meth []

(defclass MM []
(defn __matmul__ [self other]
#("matmul" other)))

(defclass Pony []
(meth set-an-attr [value]
"this is a @docstring"
(setv @attr value))
(meth use-an-attr []
(+ @attr 5))
(meth at-call []
(@set-an-attr 8))
(meth get-self []
@,)
(meth do-matmul []
(@ (MM) 2)))

(setv x (Pony))
(assert (= x.set-an-attr.__doc__ "this is a @docstring"))
(.set-an-attr x 1)
(assert (= x.attr 1))
(assert (= (.use-an-attr x) 6))
(.at-call x)
(assert (= x.attr 8))
(assert (is (.get-self x) x))
(assert (= (.do-matmul x) #("matmul" 2))))


(defn test-meth-decorated []

(defclass Pony []
(meth [classmethod] set-class-attr [value]
(setv @attr value))
(meth [example-decorator] set-instance-attr [value]
(setv @attr value)))

(assert (= Pony.set-instance-attr.da "hello"))
(setv x (Pony))
(.set-class-attr x 2)
(assert (= x.attr 2))
(assert (= Pony.attr 2))
(.set-instance-attr x 1)
(assert (= x.attr 1))
(assert (= Pony.attr 2))
(assert (= (. (Pony) attr) 2)))


(defn test-meth-init []

(setv got None)

(defclass Pony []
(meth __init__
[a1 @i1 [@i2 "i2-default"] [a2 "a2-default"] #* @ia #** @ikw]
(nonlocal got)
(setv got [a1 @i1 i2 a2 @ia @ikw])
(setv @i1 "override")))

(setv x (Pony 1 2))
(assert (= got [1 2 "i2-default" "a2-default" #() {}]))
(assert (= x.i1 "override"))
(assert (= x.i2 "i2-default"))
(assert (= x.ia #()))
(assert (= x.ikw {}))
(assert (not (hasattr x "a1")))
(assert (not (hasattr x "a2")))

(setv x (Pony 1 2 3 4 5 6 7 :foo "bar"))
(assert (= got [1 2 3 4 #(5 6 7) {"foo" "bar"}]))
(assert (= x.i1 "override"))
(assert (= x.i2 3))
(assert (= x.ia #(5 6 7)))
(assert (= x.ikw {"foo" "bar"})))


(defn test-meth-init-decorated []

(defclass Pony []
(meth [example-decorator] __init__ [@value]
(setv @attr 2)))

(assert (= Pony.__init__.da "hello"))
(setv x (Pony 1))
(assert (= x.value 1))
(assert (= x.attr 2)))


(defn test-ameth []

(defclass Pony []
(setv my-meth (ameth [value]
(setv @attr value))))

(setv x (Pony))
(.my-meth x 1)
(assert (= x.attr 1)))

0 comments on commit ccb9a48

Please sign in to comment.