Merge pull request #549 from scolsen/dynamic-map

Add Dynamic map
This commit is contained in:
Erik Svedäng 2020-04-20 23:14:03 +02:00 committed by GitHub
commit 4ceb9b513d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 152 additions and 0 deletions

View File

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

View File

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