diff --git a/docs/contrib/anaphoric.rst b/docs/contrib/anaphoric.rst index 0a487c2..0c7f584 100644 --- a/docs/contrib/anaphoric.rst +++ b/docs/contrib/anaphoric.rst @@ -1,6 +1,8 @@ -==================== +================ Anaphoric Macros -==================== +================ + +.. versionadded:: 0.9.12 The anaphoric macros module makes functional programming in Hy very concise and easy to read. @@ -94,3 +96,90 @@ the current element in the iteration. [4, 5] +.. _ap-reject: + +ap-reject +========= + +Usage: ``(ap-reject form list)`` + +This function does the opposite of ``ap-filter``, it rejects the +elements passing the predicate . The special name ``it`` is bound to +the current element in the iteration. + +.. code-block:: clojure + + => (list (ap-reject (> (* it 2) 6) [1 2 3 4 5])) + [1, 2, 3] + + +.. _ap-dotimes: + +ap-dotimes +========== + +Usage ``(ap-dotimes n body)`` + +This function evaluates the body *n* times, with the special +variable ``it`` bound from *0* to *1-n*. It is useful for side-effects. + +.. code-block:: clojure + + => (setv n []) + => (ap-dotimes 3 (.append n it)) + => n + [0, 1, 2] + + +.. _ap-first: + +ap-first +======== + +Usage ``(ap-first predfn list)`` + +This function returns the first element that passes the predicate or +``None``, with the special variable ``it`` bound to the current element in +iteration. + +.. code-block:: clojure + + =>(ap-first (> it 5) (range 10)) + 6 + + +.. _ap-last: + +ap-last +======== + +Usage ``(ap-last predfn list)`` + +This function returns the last element that passes the predicate or +``None``, with the special variable ``it`` bound to the current element in +iteration. + +.. code-block:: clojure + + =>(ap-last (> it 5) (range 10)) + 9 + + +.. _ap-reduce: + +ap-reduce +========= + +Usage ``(ap-reduce form list &optional initial-value)`` + +This function returns the result of applying form to the first 2 +elements in the body and applying the result and the 3rd element +etc. until the list is exhausted. Optionally an initial value can be +supplied so the function will be applied to initial value and the +first element instead. This exposes the element being iterated as +``it`` and the current accumulated value as ``acc``. + +.. code-block:: clojure + + =>(ap-reduce (+ it acc) (range 10)) + 45 diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index d0a163a..e12be6b 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -1,6 +1,7 @@ ;;; Hy anaphoric macros ;; ;; Copyright (c) 2013 James King +;; 2013 Abhishek L ;; ;; Permission is hereby granted, free of charge, to any person obtaining a ;; copy of this software and associated documentation files (the "Software"), @@ -61,3 +62,42 @@ (foreach [val ~lst] (if (pred val) (yield val))))) + + +(defmacro ap-reject [form lst] + "Yield elements returned when the predicate form evaluates to False" + `(ap-filter (not ~form) ~lst)) + + +(defmacro ap-dotimes [n &rest body] + "Execute body for side effects `n' times, with it bound from 0 to n-1" + (unless (numeric? n) + (raise (TypeError (.format "{0!r} is not a number" n)))) + `(ap-each (range ~n) ~@body)) + + +(defmacro ap-first [predfn lst] + "Yield the first element that passes `predfn`" + `(let [[n (gensym)]] + (ap-each ~lst (when ~predfn (setv n it) (break))) + n)) + + +(defmacro ap-last [predfn lst] + "Yield the last element that passes `predfn`" + `(let [[n (gensym)]] + (ap-each ~lst (none? n) + (when ~predfn + (setv n it))) + n)) + + +(defmacro ap-reduce [form lst &optional [initial-value None]] + "Anaphoric form of reduce, `acc' and `it' can be used for a form" + (if (none? initial-value) + `(let [[acc (car ~lst)]] + (ap-each (cdr ~lst) (setv acc ~form)) + acc) + `(let [[acc ~initial-value]] + (ap-each ~lst (setv acc ~form)) + acc))) diff --git a/tests/__init__.py b/tests/__init__.py index 582ceae..bbd24e2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,3 +12,4 @@ from .native_tests.when import * # noqa from .native_tests.with_decorator import * # noqa from .native_tests.core import * # noqa from .native_tests.reader_macros import * # noqa +from .native_tests.contrib.anaphoric import * # noqa diff --git a/tests/native_tests/contrib/__init__.hy b/tests/native_tests/contrib/__init__.hy new file mode 100644 index 0000000..e69de29 diff --git a/tests/native_tests/contrib/anaphoric.hy b/tests/native_tests/contrib/anaphoric.hy index 7fb08f4..7f88838 100644 --- a/tests/native_tests/contrib/anaphoric.hy +++ b/tests/native_tests/contrib/anaphoric.hy @@ -61,3 +61,35 @@ [3 4]) (assert-equal (list (ap-filter (even? it) [1 2 3 4])) [2 4])) + +(defn test-ap-reject [] + "NATIVE: testing anaphoric filter" + (assert-equal (list (ap-reject (> it 2) [1 2 3 4])) + [1 2]) + (assert-equal (list (ap-reject (even? it) [1 2 3 4])) + [1 3])) + +(defn test-ap-dotimes [] + "NATIVE: testing anaphoric dotimes" + (assert-equal (let [[n []]] (ap-dotimes 3 (.append n 3)) n) + [3 3 3]) + (assert-equal (let [[n []]] (ap-dotimes 3 (.append n it)) n) + [0 1 2])) + +(defn test-ap-first [] + "NATIVE: testing anaphoric first" + (assert-equal (ap-first (> it 5) (range 10)) 6) + (assert-equal (ap-first (even? it) [1 2 3 4]) 2)) + +(defn test-ap-last [] + "NATIVE: testing anaphoric last" + (assert-equal (ap-last (> it 5) (range 10)) 9) + (assert-equal (ap-last (even? it) [1 2 3 4]) 4)) + +(defn test-ap-reduce [] + "NATIVE: testing anaphoric reduce" + (assert-equal (ap-reduce (* acc it) [1 2 3]) 6) + (assert-equal (ap-reduce (* acc it) [1 2 3] 6) 36) + (assert-equal (ap-reduce (+ acc " on " it) ["Hy" "meth"]) + "Hy on meth") + (assert-equal (ap-reduce (+ acc it) [] 1) 1))