mirror of
https://github.com/carp-lang/Carp.git
synced 2024-09-11 05:25:28 +03:00
Add several utility functions to Macros.carp
This commit adds several dynamic utility functions in the spirit of map, zip, et al, including: - Compose (composer for functions of any airty, that evaluate immediately (unlike comp) - empty (returns the empty value for a structure [] or ()) - unreduce (builds a list of values) - filter (filters a list of values) - take (returns the first x members of a list) I also removed a quote in collect-into that's no longer necessary after the evaluator refactor.
This commit is contained in:
parent
2fd7a1fe40
commit
338b1624b2
174
core/Macros.carp
174
core/Macros.carp
@ -117,8 +117,7 @@
|
||||
(doc collect-into
|
||||
"Transforms a dynamic data literal into another, preserving order")
|
||||
(defndynamic collect-into [xs f]
|
||||
(list 'quote
|
||||
(collect-into-internal xs (f) f)))
|
||||
(collect-into-internal xs (f) f))
|
||||
|
||||
(doc empty?
|
||||
"Returns true if the provided data literal is empty, false otherwise.")
|
||||
@ -138,6 +137,52 @@
|
||||
(fn [x y]
|
||||
(f y x)))
|
||||
|
||||
(doc compose
|
||||
"Returns the composition of two functions `f` and `g` for functions of any
|
||||
airity; concretely, returns a function accepting the correct number of
|
||||
arguments for `g`, applies `g` to those arguments, then applies `f` to the
|
||||
result.
|
||||
|
||||
If you only need to compose functions that take a single argument (unary arity)
|
||||
see `comp`. Comp also generates the form that corresponds to the composition,
|
||||
compose contrarily evaluates 'eagerly' and returns a computed symbol.
|
||||
|
||||
For exmaple:
|
||||
|
||||
```
|
||||
;; a silly composition
|
||||
((compose empty take) 3 [1 2 3 4 5])
|
||||
;; => []
|
||||
|
||||
(String.join (collect-into ((compose reverse map) Symbol.str '(p r a c)) array))
|
||||
;; => 'carp'
|
||||
|
||||
;; comp for comparison
|
||||
((comp (curry + 1) (curry + 2)) 4)
|
||||
;; => (+ 1 (+ 2 4))
|
||||
```")
|
||||
(defndynamic compose [f g]
|
||||
;; Recall that **unquoted** function names evaluate to their definitions in
|
||||
;; dynamic contexts, e.g. f = (dyanmic f [arg] body)
|
||||
;;
|
||||
;; Right now, this cannot handle anonymous functions because they cannot be passed to apply.
|
||||
;; and not anonymous functions.
|
||||
;; commands expand to (command <name>), fns expand to a non-list.
|
||||
;;
|
||||
;; TODO: Support passing anonymous functions.
|
||||
(if (not (Dynamic.or (list? f) (list? g)))
|
||||
(macro-error "compose can only compose named dynamic functions. To
|
||||
compose anonymous functions, such as curried functions,
|
||||
see comp.")
|
||||
(let [f-name (cadr f)
|
||||
g-name (cadr g)
|
||||
arguments (caddr g)]
|
||||
(list 'fn arguments
|
||||
;; Since we call an eval to apply g immediately, we wrap the args in an
|
||||
;; extra quote, otherwise, users would need to double quote any sequence of
|
||||
;; symbols such as '(p r a c)
|
||||
(list f-name (list 'eval (list 'apply g-name (list 'quote arguments))))))))
|
||||
|
||||
;; 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]
|
||||
@ -155,7 +200,38 @@
|
||||
```")
|
||||
(defndynamic curry [f x]
|
||||
(fn [y]
|
||||
(f x y)))
|
||||
(apply f (list x y))))
|
||||
|
||||
(doc curry*
|
||||
"Curry functions that take multiple arguments.
|
||||
|
||||
The usability of this function is not great yet, in some cases, you'll
|
||||
need to pass more quotes to arguments than you might expect.
|
||||
|
||||
For example, removing the double quotes in this example will produce errors:
|
||||
|
||||
```
|
||||
(map (curry* Dynamic.zip ''+ ''(1 2 3)) '((list 1 2) (list 3 4)))
|
||||
;; => (((+ 1 1) (+ 2 2)) ((+ 1 3) (+ 2 4)))
|
||||
``
|
||||
|
||||
For basic use cases, extra quotes usually aren't necessary:
|
||||
|
||||
```
|
||||
(defndynamic add-em-up [x y z] (+ (+ x y) z))
|
||||
(map (curry* add-em-up 1 2) '(1 2 3))
|
||||
;; => (4 5 6)
|
||||
```")
|
||||
(defndynamic curry* [f :rest args]
|
||||
(let [f-name (cadr f)
|
||||
all-args (caddr f)
|
||||
unfilled-args (- (length all-args) (length args))
|
||||
remaining (take unfilled-args all-args)]
|
||||
;; eval immediately to return a closure.
|
||||
(eval (list 'fn remaining
|
||||
;; eval once more to execute the curried function.
|
||||
;; otherwise, this resolves to the form that *will* call the function, e.g. (add-three-vals 2 3 1)
|
||||
(list 'eval (list 'apply f-name (cons 'list (append args (collect-into remaining list)))))))))
|
||||
|
||||
(doc reduce
|
||||
"Reduces or 'folds' a data literal, such as a list or array, into a single
|
||||
@ -165,6 +241,92 @@
|
||||
x
|
||||
(reduce f (f x (car xs)) (cdr xs))))
|
||||
|
||||
(hidden unreduce-internal)
|
||||
(defndynamic unreduce-internal [f x lim acc counter]
|
||||
;; Currently only works with anonymous functions and named functions.
|
||||
;; does not work with commands.
|
||||
(if (not (Dynamic.or (array? acc) (list? acc)))
|
||||
(macro-error
|
||||
"Unreduce requires a dynamic data structure to collect results, such as
|
||||
(list) or (array).")
|
||||
(if (= counter lim)
|
||||
acc
|
||||
(unreduce-internal f (f x) lim (append acc (cons (eval (f x)) (empty acc))) (+ counter 1)))))
|
||||
|
||||
(doc unreduce
|
||||
"Applies `f` to a starting value `x`, then generates a sequence of values
|
||||
by successively applying `f` to the result `lim-1` times.
|
||||
Collects results in the structure given by `acc`.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
(unreduce (curry + 1) 0 10 (list))
|
||||
;; => (1 2 3 4 5 6 7 8 9 10)
|
||||
```")
|
||||
(defndynamic unreduce [f x lim acc]
|
||||
(unreduce-internal f x lim acc 0))
|
||||
|
||||
(doc filter
|
||||
"Returns a list containing only the elements of `xs` that satisify predicate `p`.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
(filter (fn [x] (= 'a x)) '(a b a b a b a b))
|
||||
;; => (a a a a)
|
||||
```")
|
||||
(defndynamic filter [p xs]
|
||||
(let [filter-fn (fn [x y] (if (p y) (append (list y) x) x))]
|
||||
(reduce filter-fn (list) xs)))
|
||||
|
||||
(doc reverse
|
||||
"Reverses the order of elements in an array or list.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
(reverse [1 2 3 4])
|
||||
;; => [4 3 2 1]
|
||||
```")
|
||||
(defndynamic reverse [xs]
|
||||
(if (array? xs)
|
||||
(reduce (flip append) (array) (map array xs))
|
||||
(reduce (flip append) (list) (map list xs))))
|
||||
|
||||
(doc empty
|
||||
"Returns the empty form of `xs`.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
(empty '(1 2 3 4))
|
||||
;; => ()
|
||||
(empty '[1 2 3 4])
|
||||
;; => []
|
||||
```")
|
||||
(defndynamic empty [xs]
|
||||
(if (array? xs)
|
||||
(array)
|
||||
(list)))
|
||||
|
||||
(doc take
|
||||
"Returns a list containing the first `n` eleements of a list.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
(take 3 '(1 2 3 4 5))
|
||||
;; => (1 2 3)
|
||||
```")
|
||||
(defndynamic take [n xs]
|
||||
;; A more straightforward impl is likely more efficient?
|
||||
(let [indicies (unreduce (curry + 1) 0 n (list))
|
||||
result (map cadr (zip list xs indicies))]
|
||||
(if (array? xs)
|
||||
(collect-into result array)
|
||||
result)))
|
||||
|
||||
(doc apply
|
||||
"Applies the function `f` to the provided argument list, passing each value
|
||||
in the list as an argument to the function.")
|
||||
@ -185,8 +347,12 @@
|
||||
;; 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.
|
||||
(if (not (list? f))
|
||||
(f argument-list)
|
||||
(let [function-name (list (cadr f))]
|
||||
(append function-name argument-list)))
|
||||
(if (array? argument-list)
|
||||
(append function-name (collect-into argument-list list))
|
||||
(append function-name argument-list)))))
|
||||
|
||||
(hidden map-internal)
|
||||
(defndynamic map-internal [f xs acc]
|
||||
|
Loading…
Reference in New Issue
Block a user