Add lazy sequences into contrib

This commit is contained in:
Tuukka Turto 2016-11-08 06:28:12 +02:00
parent 2242097b6b
commit 241d554b0b
5 changed files with 242 additions and 0 deletions

View File

@ -12,4 +12,5 @@ Contents:
flow flow
loop loop
multi multi
sequences
walk walk

View 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
View 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")))

View File

@ -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

View 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"))