Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Woven functional programming #18915

Closed
bramtayl opened this issue Oct 14, 2016 · 11 comments
Closed

Woven functional programming #18915

bramtayl opened this issue Oct 14, 2016 · 11 comments
Labels
speculative Whether the change will be implemented is speculative

Comments

@bramtayl
Copy link
Contributor

I got a request in #16285 to open up a new issue for this suggestion.

I'm copying the original post:

OK, I've gotten ChainMap into a place where it's relatively stable, and I'd like to propose an alternate method of dot vectorization based on the work there.

Here's an alternative.

  • A type, LazyCall, which contains both a function and its arguments (positionals in a tuple and keywords in an OrderedDict)
  • An @~ macro to create a LazyCall from a woven expression (this is currently @unweave in ChainMap)
  • New methods for map, broadcast, etc. which take LazyCalls.

Here's how the syntax would change:

map( ~ sin(~x + y) ) goes to map( LazyCall(x -> sin(x + y), x) )
broadcast( ~ sin(~x + ~y) ) goes to broadcast( LazyCall( (x, y) -> sin(x + y), x, y) )
mapreduce( ( ~ sin(~x + y) ), +) goes to mapreduce( LazyCall( x -> sin(x + y), x), +)

Splats (needs more thought, I think)
~ f(~(x...) ) goes to LazyCall( (x...) -> f(x...), x...)
~ f(~(;x...) ) goes to LazyCall( (;x...) -> f(;x...); x...)

Advantages over the current system:

  1. No ambiguity over what counts as a scalar and what doesn't
  2. Reduction in the amount of dots necessary for a complicated expression
  3. Support for non-broadcast operations
  4. Support for passing multiple LazyCalls to one function
  5. Partial support for lazy evaluation
  6. No need for fusion

Disadvantages:

  1. ~ is already taken as bit negation. @~ is taken for formulas. I like the symbol because it reminds me of a woven thread. But maybe another ascii character will do?
  2. Not backwards compatible; people will need to learn a new system.
  3. Less verbose
  4. Not sure about performance impacts

All of this is implemented in ChainMap at the moment, aside from the new methods for map and friends. It can probably stay in a package for now if people aren't interested in adding it to base.

@bramtayl
Copy link
Contributor Author

bramtayl commented Oct 14, 2016

And another post:

Agreed, less verbose, more flexible.

~x + ~y would just be ~x + ~y (it doesn't pass through a macro)
~ ~x + ~y would be LazyCall( (x, y) -> x + y, x, y)

In some cases, the amount of dots compared to ~ is cut down on. Eg.
sin.(cos.(tan.(x .+ 1) ) ) vs broadcast( ~ sin(cos(tan( ~x + 1) ) ) )

@bramtayl
Copy link
Contributor Author

bramtayl commented Oct 14, 2016

And another:

Agreed that the pure math aspect of things gets lost.

There are allocation free ways of going about this:

map( ~ sin(~x + y) ) goes to map( x -> sin(x + y), x)
broadcast( ~ sin(~x + ~y) ) goes to broadcast( (x, y) -> sin(x + y), x, y)
mapreduce( ( ~ sin(~x + y) ), +) goes to mapreduce( x -> sin(x + y), +, x)

And in fact, ChainMap has some convenience macros for doing this:

@map sin(_ + y) goes to map( _ -> sin(_ + y), _) (_ is used as part of the chaining)
@broadcast sin(~x + ~y) goes to broadcast( (x, y) -> sin(x + y), x, y)

But to do this you need to make assumptions about argument order (put the anonymous function first, the woven arguments last).

LazyCalls do have other advantages. They can be merged, pushed to, revised, etc. This is particularly useful for making plot calls.

@bramtayl bramtayl mentioned this issue Oct 14, 2016
5 tasks
@ChrisRackauckas
Copy link
Member

Agreed that the pure math aspect of things gets lost.

The reason why I don't see this as a usable replacement for the . syntax is because of this. There should be no reference to the programming aspect of it, the . syntax lets you write clean code on arrays that looks like math. That's why it was there in the first place. Getting rid of that interaction with math is a big step in the wrong direction, and would make code for common scientific computing applications like PDEs much more convoluted and hard to read.

@TotalVerb
Copy link
Contributor

TotalVerb commented Oct 14, 2016

@bramtayl I think this would be quite interesting. Could you make a package? The syntax can come later.

Edit: Never mind, I see that you already have https://github.com/bramtayl/ChainMap.jl

@ChrisRackauckas
Copy link
Member

ChrisRackauckas commented Oct 14, 2016

@bramtayl I think this would be quite interesting. Could you make a package? The syntax can come later.

https://github.com/bramtayl/ChainMap.jl

@bramtayl
Copy link
Contributor Author

Note that I just tagged a new version; hasn't made it into METADATA yet. Fixes mostly minor

@bramtayl
Copy link
Contributor Author

bramtayl commented Oct 14, 2016

I think if you're worried about "hiding" broadcast, it should be relatively easy to make this the "default".

For example,
sin.(~x + ~y) goes to broadcast( (x, y) -> sin(x + y), x, y) )

Although less flexible, this still retains some of the advantages:

  1. No ambiguity over what counts as a scalar and what doesn't
  2. Reduction in the amount of dots necessary for a complicated expression
  3. No need for fusion

This could be used along with syntax above

@bramtayl
Copy link
Contributor Author

bramtayl commented Oct 30, 2016

ChainMap master now has @over for this:

@over ~x + ~y goes to broadcast( (x, y) -> sin(x + y), x, y) )
@over ~x == 1 filter goes to filter( x -> x == 1, x)
@over ~x + 1 mapreduce(*) goes to mapreduce( x -> x + 1, *, x)

@yuyichao yuyichao added the speculative Whether the change will be implemented is speculative label Oct 30, 2016
@bramtayl
Copy link
Contributor Author

Ok, well, this can stay in ChainMap.jl for now.

@bramtayl
Copy link
Contributor Author

I've been thinking about this again. Currently Call in LazyCall is immutable. Am I correct in my understanding that it will be unrolled during optimizations, and its use won't require additional allocations?

@KristofferC
Copy link
Member

I think discourse is better for general julia questions like this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
speculative Whether the change will be implemented is speculative
Projects
None yet
Development

No branches or pull requests

5 participants