By "inside" I mean inside the parentheses, unlike normal functors which are written outside the parentheses.
This really starts with a grammatical detail. Sentences like
> sapply(ns, function(n) {
> sum(1:n)
> })
translate into English "Take the list of, for every element in
ns
the sum of 1 to n." This is fine for coding but that's not at
all how you'd actually talk.
What if the code were written like
> sum(1:each(ns))
Which sounds like "Take the sum from 1 to each of the ns", which I think sounds more natural. The challenge: make that code run.
Let's make things easier for a moment and consider just functions of 1 variable. Clearly we are going to have to change what is meant by "calling" a function. So forget nice syntax for a moment and define
> apply.check.functor = function(func, arg) {
> if (is.inside.functor(arg)) {
> apply.functor(arg, func, arg)
> }
> else {
> func(arg)
> }
> }
Here we assume apply.functor
is a method that individual functor
classes will define.
Then we can define each
like
> each = function(arg) {
> inside = list(items = arg)
> class(inside) = "each"
> inside
> }
> apply.functor.each = function(inside, func, arg) {
> each(lapply(inside$items, function(x) {
> apply.check.functor(func, x)
> }))
> }
> is.inside.functor.each = function(inside) {
> T
> }
And you can see this is working exactly like fmap
from say
Haskell.
Then add those methods we needed,
> is.inside.functor = function(...) {
> UseMethod("is.inside.functor")
> }
> is.inside.functor.default = function(...) {
> F
> }
> apply.functor = function(...) {
> UseMethod("apply.functor")
> }
And test:
> sum.until = function(n) {
> sum(1:n)
> }
> x = c(1, 2, 3)
> x
1 2 3
> y = apply.check.functor(sum.until, each(x))$items
> y
-
1
-
3
-
6
And they can be nested:
> z = apply.check.functor(round, apply.check.functor(sin, each(x)))$items
> z
-
1
-
1
-
0
And now to make the syntax nicer. Rather than calling
apply.check.functor
each time, the function being called can do
that itself:
> fmap = function(func) {
> params = formals(args(func))
> new.func = function() {
> .args = as.list(environment())
> apply.check.functor(func, .args[[1]])
> }
> formals(new.func) = params
> new.func
> }
> sum.until. = fmap(sum.until)
> y = sum.until.(each(x))$items
> y
-
1
-
3
-
6
(This version of fmap
has several technical problems which I'll
point out later).
BUT our original example had a function of 2 arguments. If we're going to start handling multiple arguments, we have to answer a few questions:
- What do we do if some arguments are
inside.functor
s and others are not? - How do we handle multiple disagreeing
inside.functor
s?
Take the first one. If you really want to imitate English (which I do), it's only inevitable that
> each(x) + 1
should add 1 to each x. So the argument 1
should be "brought in" to the
functor.
For the second question, take the example
> each(x) + each(y)
where clearly the intent is to "line up" the corresponding elements of x and y. Actually, to closer follow English you would say
> each(x) + corresponding(y)
Which might be preferable because it is less ambiguous.
More will follow.
No comments:
Post a Comment