thautwarm / MLStyle.jl

Julia functional programming infrastructures and metaprogramming facilities
https://thautwarm.github.io/MLStyle.jl/latest/
MIT License
404 stars 39 forks source link

Runtime generated patterns #95

Closed MasonProtter closed 4 years ago

MasonProtter commented 4 years ago

I suspect this might be a bit antithetical to the idea of MLStyle as being able to be runtime free, but I think there are cases where you'd really want to be able to specify the pattern at runtime instead of macroexpansion time, even if there is a large performance penalty.

I'm imagining something like

pats = [
    (Paper,    Rock)      => "Paper Wins!";
    (Rock,     Scissors)  => "Rock Wins!";
    (Scissors, Paper)     => "Scissors Wins!";
    (a, b)                => a == b ? "Tie!" : play(b, a)
]
match((a, b), pats)

This can be useful if you're programatically generating patterns. It's something I ran into a bit with https://github.com/MasonProtter/PatternDispatch.jl which I found rather frustrating.

thautwarm commented 4 years ago

I know what you want.. Although just as what you said, it's antithetical to some goals of MLStyle.

The most flexible class of pattern matching is called first-class pattern matching, in which patterns can be constructed in purely runtime.

function f(pat, x)
    match(pat => "Ok", wildcard => "Error")(x)
end

or

function f(pat, x)
   case1 = Case(pat, ctx -> ctx.some_var)
   case2 = Case(wildcard, _ -> 0)
   match([case1, case2], x)
end

It's something I ran into a bit with https://github.com/MasonProtter/PatternDispatch.jl which I found rather frustrating.

Could you explain your concerns somewhere and share them with me?

Issues

First-class pattern matching will have the performance issues and impediments about optimization .

Julia's optimizations for constructing data types are not that advanced so far, hence first-class patterns can introduce a lot of memory allocations and bring notable overhead.

Besides, some performance optimizations for pattern matching is expensive to runtime, and usually used in static languages, so as to have practical first-class patterns in Julia, we have to drop these optimizations.

You mentioned generated patterns

Advantages

Advantages of first-class patterns are quite charming:

Appendix: PEP 622

So far Python guys are actually introducing first-class patterns, although I don't know if they aware of this fact.

https://www.python.org/dev/peps/pep-0622/#id10

They used to have a __match__ protocol, you can specify the behavior of C(a, b, ...) by providing a proper C.__match__.

For patterns like [1, 2, 3] or (1, 2, 3), we can refer to Array.__match__ and Tuple.__match__.

thautwarm commented 4 years ago

If possible I hope you could open a thread at discourse. It's quite interesting that I feel like to discuss.

However, this is technically impossible for MLStyle and also violate the design of MLStyle, I have to close it.

0x0f0f0f commented 3 years ago

I managed to achieve this behaviour with RuntimeGeneratedFunctions.jl and MatchCore.jl. I will make the code public as soon as it's ready. Performance wise, it takes a second to warm up. I think I could get better performance by "pre-generating" the pattern matching runtime generated function that does the job.

It even works at macro expansion time! The main downside is that when matching at runtime you have to specify in which module you are executing the pattern matching block to access externally defined variables though.