diff --git a/core/Macros.carp b/core/Macros.carp index e76c4f91..dee4cff8 100644 --- a/core/Macros.carp +++ b/core/Macros.carp @@ -119,6 +119,142 @@ (defndynamic collect-into [xs f] (list 'quote (collect-into-internal xs (f) f))) + + (doc empty? + "Returns true if the provided data literal is empty, false otherwise.") + (defndynamic empty? [xs] + (= 0 (length xs))) + + (doc flip + "Flips the arguments of a function `f`. + + For example, + + ``` + ((flip Symbol.prefix) 'Bar 'Foo) + => ;; (Foo.Bar) + ```") + (defndynamic flip [f] + (fn [x y] + (f y x))) + + ;; Dynamic.or already exists, but since it's a special form, it can't be passed + ;; to higher order functions like reduce. So, we define an alternative here. + (defndynamic or-internal [x y] + (if x true y)) + + (doc curry + "Returns a curried function accepting a single argument, that applies f to x + and then to the following argument. + + For example, + + ``` + (map (curry Symbol.prefix 'Foo) '(bar baz)) + ;; => (Foo.bar Foo.baz) + ```") + (defndynamic curry [f x] + (fn [y] + (f x y))) + + (doc reduce + "Reduces or 'folds' a data literal, such as a list or array, into a single + value through successive applications of `f`.") + (defndynamic reduce [f x xs] + (if (empty? xs) + x + (reduce f (f x (car xs)) (cdr xs)))) + + (doc apply + "Applies the function `f` to the provided argument list, passing each value + in the list as an argument to the function.") + (defndynamic apply [f argument-list] + ;; The let clause here is a tad mysterious at first glance. When passed a + ;; standalone function name (i.e. not an application (f x), carp evaluates + ;; it into the function's defining form, e.g. foo becomes (defn foo [x] x), + ;; commands such as + become (command +) etc. ;; The binding here accounts + ;; for that case, allowing users to pass the function name to apply + ;; unquoted. + ;; + ;; This is necessary for parity across map-internal, zip, and apply. + ;; Since map calls its function directly, it takes it as is. Apply, on the + ;; other hand, would have to take the quoted form, since it builds a list + ;; that serves as the actual application. + ;; + ;; This is problematic for the user facing map function, since it makes + ;; calls to map or zip (which uses apply) as appropriate--unless we support + ;; the quoted function name argument in map-internal or the unquoted one in + ;; apply, we can't use zip and map-internal in map. + (let [function-name (list (cadr f))] + (append function-name argument-list))) + + (hidden map-internal) + (defndynamic map-internal [f xs acc] + (if (empty? xs) + acc + (map-internal f (cdr xs) (cons-last (f (car xs)) acc)))) + + (hidden zip-internal) + (defndynamic zip-internal [f forms acc] + (if (reduce or-internal false (map-internal empty? forms (list))) + acc + (zip-internal + f + (map-internal cdr forms (list)) + (let [result (list (apply f (map-internal car forms (list))))] + (append acc result))))) + + (doc zip + "Returns the *form* that results from applying a function `f` to each of + the values supplied in `forms`. + + If the members of a single form are exhuasted, the result of the + applications thus far is returned, and any remaining members in the other + forms are ignored. + + For example, + + ``` + (zip + '(1 2 3) '(4 5 6)) + ;; => ((+ 1 4) (+ 2 5) (+ 3 6)) + ``` + + It's important to note that zip operates on forms, and that the form + returned by zip may not be evaluable by itself. For instance, to actually + transform the result in the example above into something Carp can + evaluate, we need to wrap each member of the list in a `do`: + + ``` + (append (list 'do) (zip + '(1 2 3) '(4 5 6))) + ;; => (do (+ 1 4) (+ 2 5) (+ 3 6)) + (eval (append (list 'do) (zip + '(1 2 3) '(4 5 6)))) + ;; => 9 ;; do returns the value of the last form in its body + ```") + (defndynamic zip [f :rest forms] + (zip-internal f forms (list))) + + (doc map + "Applies a function `f` to `forms` and returns a list dynamic data literal + containing the result of the function applications. If a single form is + provided, the function is applied to each member of the form. If multiple + forms are provided, the function is applied to the members of each form in + succession. If the members of a single form are exhuasted, the result of the + applications thus far is returned, and any remaining members in the other + forms are ignored. + + For example: + ```clojure + '(map symbol? '(a b c)) + => (true true true) + '(map + '(1 2 3) '(4 5 6)) + => ((+ 1 4) (2 5) (6 3)) + '(map + '(1 2 3) '(4 5 6) '(7)) + => ((+ 1 4 7)) + ```") + (defndynamic map [f :rest forms] + (if (= 1 (length forms)) + (map-internal f (car forms) (list)) + (zip-internal f forms (list)))) ) (defndynamic cond-internal [xs] diff --git a/test/macros.carp b/test/macros.carp index 03d88268..9e2f1b86 100644 --- a/test/macros.carp +++ b/test/macros.carp @@ -58,6 +58,16 @@ (let [x (gensym-with 'a)] (list 'let (array x 1) (list '= x 1)))) +(defmacro test-map [] + (let [mapped (Dynamic.map length '((a) (b c) (d e f)))] + (Dynamic.and (Dynamic.and (= 1 (Dynamic.car mapped)) (= 2 (Dynamic.cadr mapped))) (= 3 + (Dynamic.caddr mapped))))) + +(defmacro test-zip [] + (let [zipped (Dynamic.zip array '('a 'd) '('c 'o) '('e 'g))] + (Dynamic.and (= 'ace (Symbol.join (eval (Dynamic.car zipped)))) + (= 'dog (Symbol.join (eval (Dynamic.cadr zipped))))))) + (deftest test (assert-true test (test-let-do) @@ -221,4 +231,10 @@ (assert-true test (test-gensym) "gensym works as expected") + (assert-true test + (test-map) + "map works as expected") + (assert-true test + (test-zip) + "zip works as expected") )