Saturday, April 23, 2011

"Inside" Functors

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:

  1. What do we do if some arguments are inside.functors and others are not?
  2. How do we handle multiple disagreeing inside.functors?

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