Add lazy sequences into contrib
This commit is contained in:
parent
2242097b6b
commit
241d554b0b
@ -12,4 +12,5 @@ Contents:
|
|||||||
flow
|
flow
|
||||||
loop
|
loop
|
||||||
multi
|
multi
|
||||||
|
sequences
|
||||||
walk
|
walk
|
||||||
|
77
docs/contrib/sequences.rst
Normal file
77
docs/contrib/sequences.rst
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
==============
|
||||||
|
Lazy sequences
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. versionadded:: 0.12.0
|
||||||
|
|
||||||
|
Sequences module contains few macros for declaring sequences that are evaluated
|
||||||
|
only as much as the client code requests elements. Compared to generators they
|
||||||
|
allow accessing same element multiple times. Since they cache calculated
|
||||||
|
values, they aren't suited for infinite sequences. However, the implementation
|
||||||
|
allows recursive definition of sequences, without resulting recursive
|
||||||
|
computation.
|
||||||
|
|
||||||
|
To use these macros you need to require them and import other types like:
|
||||||
|
|
||||||
|
.. code-block:: hy
|
||||||
|
|
||||||
|
(require [hy.contrib.sequences [defseq seq]])
|
||||||
|
(import [hy.contrib.sequences [Sequence end-sequence]])
|
||||||
|
|
||||||
|
Simplest sequence can be defined as: ``(seq [n] n)``. This defines a sequence
|
||||||
|
that starts as: ``[0 1 2 3 ...]`` and continues forever. In order to define
|
||||||
|
a finite sequence, ``end-sequence`` needs to be called to signal end of
|
||||||
|
the sequence:
|
||||||
|
|
||||||
|
.. code-block:: hy
|
||||||
|
|
||||||
|
(seq [n]
|
||||||
|
(cond [(< n 5) n]
|
||||||
|
[true (end-sequence)]))
|
||||||
|
|
||||||
|
This creates following sequence: ``[0 1 2 3 4]``. For such a sequence ``len``
|
||||||
|
returns amount of items in sequence and negative indexing is suported. Because
|
||||||
|
both of thse require evaluating whole sequence, calling such a function would
|
||||||
|
take forever (or at least until available memory has been exhausted).
|
||||||
|
|
||||||
|
Sequence can be defined recursively. Canonical example of fibonacci numbers
|
||||||
|
is defined as:
|
||||||
|
|
||||||
|
.. code-block:: hy
|
||||||
|
|
||||||
|
(defseq fibonacci [n]
|
||||||
|
(cond [(= n 0) 0]
|
||||||
|
[(= n 1) 1]
|
||||||
|
[true (+ (get fibonacci (- n 1))
|
||||||
|
(get fibonacci (- n 2)))]))
|
||||||
|
|
||||||
|
This results sequence of ``[0 1 1 2 3 5 8 13 21 34 ...]``.
|
||||||
|
|
||||||
|
.. _seq:
|
||||||
|
|
||||||
|
seq
|
||||||
|
===
|
||||||
|
|
||||||
|
Usage: ``(seq [n] (* n n)``
|
||||||
|
|
||||||
|
Creates a sequence defined in terms of ``n``.
|
||||||
|
|
||||||
|
.. _defseq:
|
||||||
|
|
||||||
|
defseq
|
||||||
|
======
|
||||||
|
|
||||||
|
Usage: ``(defseq numbers [n] n)``
|
||||||
|
|
||||||
|
Creates a sequence defined in terms of ``n`` and assigns it to a given name.
|
||||||
|
|
||||||
|
.. _end-sequence:
|
||||||
|
|
||||||
|
end-sequence
|
||||||
|
============
|
||||||
|
|
||||||
|
Usage: ``(seq [n] (if (< n 5) n (end-sequence)))``
|
||||||
|
|
||||||
|
Signals end of a sequence when iterator reaches certain point of sequence.
|
||||||
|
Internally this is done by raising ``IndexError``, catching that in iterator
|
||||||
|
and raising ``StopIteration``.
|
78
hy/contrib/sequences.hy
Normal file
78
hy/contrib/sequences.hy
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
;; Copyright (c) 2016 Tuukka Turto <tuukka.turto@oktaeder.net>
|
||||||
|
;;
|
||||||
|
;; Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
;; copy of this software and associated documentation files (the "Software"),
|
||||||
|
;; to deal in the Software without restriction, including without limitation
|
||||||
|
;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
;; and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
;; Software is furnished to do so, subject to the following conditions:
|
||||||
|
;;
|
||||||
|
;; The above copyright notice and this permission notice shall be included in
|
||||||
|
;; all copies or substantial portions of the Software.
|
||||||
|
;;
|
||||||
|
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
;; DEALINGS IN THE SOFTWARE.
|
||||||
|
;;
|
||||||
|
|
||||||
|
(defclass Sequence []
|
||||||
|
[--init-- (fn [self func]
|
||||||
|
"initialize a new sequence with a function to compute values"
|
||||||
|
(setv (. self func) func)
|
||||||
|
(setv (. self cache) [])
|
||||||
|
(setv (. self high-water) -1)
|
||||||
|
nil)
|
||||||
|
--getitem-- (fn [self n]
|
||||||
|
"get nth item of sequence"
|
||||||
|
(if (hasattr n "start")
|
||||||
|
(if n.step
|
||||||
|
(genexpr (get self x) [x (range n.start n.stop n.step)])
|
||||||
|
(genexpr (get self x) [x (range n.start n.stop 1)]))
|
||||||
|
(do (when (neg? n)
|
||||||
|
(len self))
|
||||||
|
(if (<= n (. self high-water))
|
||||||
|
(get (. self cache) n)
|
||||||
|
(do (while (< (. self high-water) n)
|
||||||
|
(setv (. self high-water) (inc (. self high-water)))
|
||||||
|
(.append (. self cache) (.func self (. self high-water))))
|
||||||
|
(get self n))))))
|
||||||
|
--iter-- (fn [self]
|
||||||
|
"create iterator for this sequence"
|
||||||
|
(setv index 0)
|
||||||
|
(try (while true
|
||||||
|
(yield (get self index))
|
||||||
|
(setv index (inc index)))
|
||||||
|
(except [_ IndexError]
|
||||||
|
(raise StopIteration))))
|
||||||
|
--len-- (fn [self]
|
||||||
|
"length of the sequence, dangerous for infinite sequences"
|
||||||
|
(setv index (. self high-water))
|
||||||
|
(try (while true
|
||||||
|
(get self index)
|
||||||
|
(setv index (inc index)))
|
||||||
|
(except [_ IndexError]
|
||||||
|
(len (. self cache)))))
|
||||||
|
--str-- (fn [self]
|
||||||
|
"string representation of this sequence"
|
||||||
|
(setv items (list (take 11 self)))
|
||||||
|
(.format (if (= (len items) 11)
|
||||||
|
"[{0}, ...]"
|
||||||
|
"[{0}]")
|
||||||
|
(.join ", " (map str items))))
|
||||||
|
--repr-- (fn [self]
|
||||||
|
"string representation of this sequence"
|
||||||
|
(.--str-- self))])
|
||||||
|
|
||||||
|
(defmacro seq [param seq-code]
|
||||||
|
`(Sequence (fn ~param ~seq-code)))
|
||||||
|
|
||||||
|
(defmacro defseq [seq-name param seq-code]
|
||||||
|
`(def ~seq-name (seq ~param ~seq-code)))
|
||||||
|
|
||||||
|
(defn end-sequence []
|
||||||
|
"raise IndexError exception to signal end of sequence"
|
||||||
|
(raise (IndexError "list index out of range")))
|
@ -21,6 +21,7 @@ from .native_tests.contrib.walk import * # noqa
|
|||||||
from .native_tests.contrib.multi import * # noqa
|
from .native_tests.contrib.multi import * # noqa
|
||||||
from .native_tests.contrib.curry import * # noqa
|
from .native_tests.contrib.curry import * # noqa
|
||||||
from .native_tests.contrib.botsbuildbots import * # noqa
|
from .native_tests.contrib.botsbuildbots import * # noqa
|
||||||
|
from .native_tests.contrib.sequences import * # noqa
|
||||||
|
|
||||||
if PY3:
|
if PY3:
|
||||||
from .native_tests.py3_only_tests import * # noqa
|
from .native_tests.py3_only_tests import * # noqa
|
||||||
|
85
tests/native_tests/contrib/sequences.hy
Normal file
85
tests/native_tests/contrib/sequences.hy
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
;; Copyright (c) 2016 Tuukka Turto <tuukka.turto@oktaeder.net>
|
||||||
|
;;
|
||||||
|
;; Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
;; copy of this software and associated documentation files (the "Software"),
|
||||||
|
;; to deal in the Software without restriction, including without limitation
|
||||||
|
;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
;; and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
;; Software is furnished to do so, subject to the following conditions:
|
||||||
|
;;
|
||||||
|
;; The above copyright notice and this permission notice shall be included in
|
||||||
|
;; all copies or substantial portions of the Software.
|
||||||
|
;;
|
||||||
|
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
;; DEALINGS IN THE SOFTWARE.
|
||||||
|
;;
|
||||||
|
|
||||||
|
(require [hy.contrib.sequences [seq defseq]])
|
||||||
|
|
||||||
|
(import [hy.contrib.sequences [Sequence end-sequence]])
|
||||||
|
|
||||||
|
(defn test-infinite-sequence []
|
||||||
|
"NATIVE: test creating infinite sequence"
|
||||||
|
(assert (= (list (take 5 (seq [n] n)))
|
||||||
|
[0 1 2 3 4])))
|
||||||
|
|
||||||
|
(defn test-indexing-sequence []
|
||||||
|
"NATIVE: test indexing sequence"
|
||||||
|
(defseq shorty [n]
|
||||||
|
(cond [(< n 10) n]
|
||||||
|
[true (end-sequence)]))
|
||||||
|
(assert (= (get shorty 0)
|
||||||
|
(get (range 10) 0))
|
||||||
|
"getting first element failed")
|
||||||
|
(assert (= (get shorty 5)
|
||||||
|
(get (range 10) 5))
|
||||||
|
"getting 5th element failed")
|
||||||
|
(assert (= (get shorty -1)
|
||||||
|
(get (range 10) -1))
|
||||||
|
"getting element -1 failed"))
|
||||||
|
|
||||||
|
(defn test-slicing-sequence []
|
||||||
|
"NATIVE: test slicing sequence"
|
||||||
|
(defseq shorty [n]
|
||||||
|
(cond [(< n 10) n]
|
||||||
|
[true (end-sequence)]))
|
||||||
|
(assert (= (first shorty)
|
||||||
|
(first (range 10)))
|
||||||
|
"getting first failed")
|
||||||
|
(assert (= (list (rest shorty))
|
||||||
|
(list (rest (range 10))))
|
||||||
|
"getting rest failed")
|
||||||
|
(assert (= (list (cut shorty 2 6))
|
||||||
|
(list (cut (range 10) 2 6)))
|
||||||
|
"cutting 2-6 failed")
|
||||||
|
(assert (= (list (cut shorty 2 8 2))
|
||||||
|
(list (cut (range 10) 2 8 2)))
|
||||||
|
"cutting 2-8-2 failed")
|
||||||
|
(assert (= (list (cut shorty 8 2 -2))
|
||||||
|
(list (cut (range 10) 8 2 -2)))
|
||||||
|
"negative cut failed"))
|
||||||
|
|
||||||
|
(defn test-recursive-sequence []
|
||||||
|
"NATIVE: test defining a recursive sequence"
|
||||||
|
(defseq fibonacci [n]
|
||||||
|
(cond [(= n 0) 0]
|
||||||
|
[(= n 1) 1]
|
||||||
|
[true (+ (get fibonacci (- n 1))
|
||||||
|
(get fibonacci (- n 2)))]))
|
||||||
|
(assert (= (first fibonacci)
|
||||||
|
0)
|
||||||
|
"first element of fibonacci didn't match")
|
||||||
|
(assert (= (second fibonacci)
|
||||||
|
1)
|
||||||
|
"second element of fibonacci didn't match")
|
||||||
|
(assert (= (get fibonacci 40)
|
||||||
|
102334155)
|
||||||
|
"40th element of fibonacci didn't match")
|
||||||
|
(assert (= (list (take 9 fibonacci))
|
||||||
|
[0 1 1 2 3 5 8 13 21])
|
||||||
|
"taking 8 elements of fibonacci didn't match"))
|
Loading…
Reference in New Issue
Block a user