diff --git a/docs/contrib/index.rst b/docs/contrib/index.rst index ba0e3a4..53dcf23 100644 --- a/docs/contrib/index.rst +++ b/docs/contrib/index.rst @@ -9,3 +9,4 @@ Contents: anaphoric loop + multi diff --git a/docs/contrib/multi.rst b/docs/contrib/multi.rst new file mode 100644 index 0000000..b8f3619 --- /dev/null +++ b/docs/contrib/multi.rst @@ -0,0 +1,23 @@ +======== +defmulti +======== + +.. versionadded:: 0.9.13 + +`defmulti` lets you arity-overload a function by the given number of +args and/or kwargs. Inspired by clojures take on `defn`. + +.. code-block:: clj + + => (require hy.contrib.multi) + => (defmulti fun + ... ([a] a) + ... ([a b] "a b") + ... ([a b c] "a b c")) + => (fun 1 2 3) + 'a b c' + => (fun a b) + "a b" + => (fun 1) + 1 + diff --git a/hy/contrib/dispatch/__init__.py b/hy/contrib/dispatch/__init__.py new file mode 100644 index 0000000..a14091b --- /dev/null +++ b/hy/contrib/dispatch/__init__.py @@ -0,0 +1,50 @@ +# -*- encoding: utf-8 -*- +# +# Decorator for defmulti +# +# Copyright (c) 2014 Morten Linderud +# +# 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. + +from collections import defaultdict + + +class MultiDispatch(object): + _fns = defaultdict(dict) + + def __init__(self, fn): + self.fn = fn + self.__doc__ = fn.__doc__ + if fn.__name__ not in self._fns[fn.__module__].keys(): + self._fns[fn.__module__][fn.__name__] = {} + values = fn.__code__.co_varnames + self._fns[fn.__module__][fn.__name__][values] = fn + + def is_fn(self, v, args, kwargs): + """Compare the given (checked fn) too the called fn""" + com = list(args) + list(kwargs.keys()) + if len(com) == len(v): + return all([kw in com for kw in kwargs.keys()]) + return False + + def __call__(self, *args, **kwargs): + for i, fn in self._fns[self.fn.__module__][self.fn.__name__].items(): + if self.is_fn(i, args, kwargs): + return fn(*args, **kwargs) + raise TypeError("No matching functions with this signature!") diff --git a/hy/contrib/multi.hy b/hy/contrib/multi.hy new file mode 100644 index 0000000..19246ee --- /dev/null +++ b/hy/contrib/multi.hy @@ -0,0 +1,41 @@ +;; Hy Arity-overloading +;; Copyright (c) 2014 Morten Linderud + +;; 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. + +(import [collections [defaultdict]]) +(import [hy.models.string [HyString]]) + + +(defmacro defmulti [name &rest bodies] + (def comment (HyString)) + (if (= (type (first bodies)) HyString) + (do (def comment (car bodies)) + (def bodies (cdr bodies)))) + + (def ret `(do)) + + (.append ret '(import [hy.contrib.dispatch [MultiDispatch]])) + + (for [body bodies] + (def let-binds (car body)) + (def body (cdr body)) + (.append ret + `(with-decorator MultiDispatch (defn ~name ~let-binds ~comment ~@body)))) + ret) diff --git a/tests/__init__.py b/tests/__init__.py index e36ce11..f336ad0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -18,3 +18,4 @@ from .native_tests.contrib.anaphoric import * # noqa from .native_tests.contrib.loop import * # noqa from .native_tests.contrib.meth import * # noqa from .native_tests.contrib.walk import * # noqa +from .native_tests.contrib.multi import * # noqa diff --git a/tests/native_tests/contrib/multi.hy b/tests/native_tests/contrib/multi.hy new file mode 100644 index 0000000..5ce9932 --- /dev/null +++ b/tests/native_tests/contrib/multi.hy @@ -0,0 +1,57 @@ +;; Copyright (c) 2014 Morten Linderud + +;; 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.multi) + + +(defn test-basic-multi [] + "NATIVE: Test a basic defmulti" + (defmulti fun + ([] "Hello!") + ([a] a) + ([a b] "a b") + ([a b c] "a b c")) + + (assert (= (fun) "Hello!")) + (assert (= (fun "a") "a")) + (assert (= (fun "a" "b") "a b")) + (assert (= (fun "a" "b" "c") "a b c"))) + + +(defn test-kw-args [] + "NATIVE: Test if kwargs are handled correctly" + (defmulti fun + ([a] a) + ([&optional [a "nop"] [b "p"]] (+ a b))) + + (assert (= (fun 1) 1)) + (assert (= (apply fun [] {"a" "t"}) "t")) + (assert (= (apply fun ["hello "] {"b" "world"}) "hello world")) + (assert (= (apply fun [] {"a" "hello " "b" "world"}) "hello world"))) + + +(defn test-docs [] + "NATIVE: Test if docs are properly handled" + (defmulti fun + "docs" + ([a] (print a)) + ([a b] (print b))) + + (assert (= fun.--doc-- "docs")))