Raku / doc

🦋 Raku documentation
https://docs.raku.org/
Artistic License 2.0
286 stars 293 forks source link

Explain more on benefits/use-case of using `is pure` trait #1231

Closed trosel closed 6 years ago

trosel commented 7 years ago

https://docs.perl6.org/routine/is%20pure

Even after reading about what constant-folding is, it's not clear why someone would need to explicitly mark this when the compiler can probably infer it on its own.

Is there a difference between using is pure on a sub that has a return value vs on a sub that doesn't have a return value?

AlexDaniel commented 7 years ago

Can somebody review what I just did? Does it resolve the issue?

rafaelschipiura commented 7 years ago

Yep, makes it very clear.

JJ commented 7 years ago

I don't pretend to reopen this issue, and what I'm about to say is not specific to is pure. Wouldn't it be better to look for actual use cases and insert them as examples? While multiplying input by 2 is syntactically correct, it's relatively unlikely to meet it in a real life situation; you will simply multiply by 2 without needing to create a function. Not that examples elsewhere are better: https://medium.com/@jamesjefferyuk/javascript-what-are-pure-functions-4d4d5392d49c but this http://www.deadcoderising.com/2017-06-13-why-pure-functions-4-benefits-to-embrace-2/ for instance mentions composition, laziness, both of them having support in perl 6, and maybe they could be mentioned here in this example. Since I do have write permission here, would you like me to try something here along these lines? Just reopen the issue and assign it to me if you want.

rafaelschipiura commented 7 years ago

@JJ Perl 6 can have composition, laziness and concurrency on normal functions just fine. The trait is used to change their performance characteristics, moving a runtime calculation (which could be cached at runtime too: https://docs.perl6.org/routine/is%20cached) into compilation time.

moritz commented 7 years ago

@JJ feel free to provide a better example

JJ commented 7 years ago

2017-09-03 10:24 GMT+02:00 rafaelschipiura notifications@github.com:

@JJ https://github.com/jj Perl 6 can have composition, laziness and concurrency on normal functions just fine. The trait is used to change their performance characteristics, moving a runtime calculation (which could be cached at runtime too: https://docs.perl6.org/routine/is%20cached) into compilation time.

I didn't mean those were exclusive traits of pure functions. My point was that it would be interesting to provide meaningful use cases by providing examples that fall in them.

rafaelschipiura commented 7 years ago

@JJ I agree your idea is good, I was just trying to help find a good example.

JJ commented 7 years ago

2017-09-03 10:28 GMT+02:00 rafaelschipiura notifications@github.com:

@JJ https://github.com/jj I agree your idea is good, I was just trying to help find a good example.

Thanks. In fact, caching/memoization is another use case of pure routines. I'll try to find examples for all of them, maybe run some measurements too.

rafaelschipiura commented 7 years ago

@JJ I just want you to note that the very example provided in the documentation has side effects and Perl6 has no problem with it, OK?

JJ commented 6 years ago

I've come with a nice use case that includes pre-computing a set of things; however it involves using experimental macros. Would this be suitable? Or is it better if I think about a different one? I'm not sure about phasers here. Would macro go before or after is pure evaluation? I'd have to check for that. Without macros, all examples are going to be a variation of "call this function with a constant, then call another function with a constant". For producing constants at compile time in a reasonable way, that's what macros are for.

AlexDaniel commented 6 years ago

however it involves using experimental macros. Would this be suitable?

No.

zoffixznet commented 6 years ago

Would this be suitable?

👎 even if they weren't experimental. There's no need to daunt the user with a whole 'nother large language feature they'd have to be familiar with just to explain is pure

all examples are going to be a variation of "call this function with a constant

is pure routines don't need to be called with constants in examples. is pure is a hint and just because not all cases of routine call would use constants doesn't mean you can't mark it as pure. There are plenty of colourful routines in core that are marked as pure. Surely a reasonably-simple real-world example can be constructed with routines similar to them:

$ grep -FR 'is pure' src/
src/core/set_addition.pm:proto sub infix:<(+)>(|) is pure {*}
src/core/Range.pm:sub infix:<..>($min, $max) is pure {
src/core/Range.pm:sub infix:<^..>($min, $max) is pure {
src/core/Range.pm:sub infix:<..^>($min, $max) is pure {
src/core/Range.pm:sub infix:<^..^>($min, $max) is pure {
src/core/Range.pm:sub prefix:<^>($max) is pure {
src/core/set_subset.pm:proto sub infix:<<(<=)>>($, $ --> Bool:D) is pure {*}
src/core/set_subset.pm:only sub infix:<⊈>($a, $b --> Bool:D) is pure {
src/core/set_subset.pm:only sub infix:<⊇>($a, $b --> Bool:D) is pure {
src/core/set_subset.pm:only sub infix:<⊉>($a, $b --> Bool:D) is pure {
src/core/Pair.pm:sub infix:«=>»(Mu $key, Mu \value) is pure {
src/core/Pair.pm:sub pair(Mu $key, \value) is pure {
src/core/Stringy.pm:proto sub prefix:<~>($) is pure {*}
src/core/Stringy.pm:proto sub infix:<~>(|) is pure {*}
src/core/Stringy.pm:proto sub infix:<x>(Mu $?, Mu $?)  is pure {*}
src/core/Stringy.pm:proto sub infix:<leg>(Mu $?, Mu $?) is pure {*}
src/core/Stringy.pm:proto sub infix:<eq>(Mu $?, Mu $?)  is pure {*}
src/core/Stringy.pm:proto sub infix:<ne>(Mu $?, Mu $?) is pure {*}
src/core/Stringy.pm:proto sub infix:<lt>(Mu $?, Mu $?) is pure {*}
src/core/Stringy.pm:proto sub infix:<le>(Mu $?, Mu $?) is pure {*}
src/core/Stringy.pm:proto sub infix:<gt>(Mu $?, Mu $?) is pure {*}
src/core/Stringy.pm:proto sub infix:<ge>(Mu $?, Mu $?) is pure {*}
src/core/Stringy.pm:proto sub infix:<~|>(Mu $?, Mu $?) is pure {*}
src/core/Stringy.pm:proto sub infix:<~^>(Mu $?, Mu $?)  is pure {*}
src/core/Stringy.pm:proto sub infix:<~&>(Mu $?, Mu $?) is pure {*}
src/core/Stringy.pm:proto sub prefix:<~^>(Mu $) is pure {*}
src/core/set_elem.pm:proto sub infix:<(elem)>($, $ --> Bool:D) is pure {*}
src/core/set_elem.pm:only sub infix:<∉>($a, $b --> Bool:D) is pure {
src/core/set_elem.pm:only sub infix:<(cont)>($a, $b --> Bool:D) is pure { $b (elem) $a }
src/core/set_elem.pm:only sub infix:<∋>($a, $b --> Bool:D) is pure {
src/core/set_elem.pm:only sub infix:<∌>($a, $b --> Bool:D) is pure {
src/core/Cool.pm:proto sub rindex($, $, $?) is pure {*};
src/core/Cool.pm:proto sub ords($) is pure     {*}
src/core/Cool.pm:proto sub wordcase($) is pure {*}
src/core/Cool.pm:proto sub chars($) is pure {*}
src/core/Bool.pm:proto sub prefix:<?>(Mu $) is pure {*}
src/core/Bool.pm:proto sub prefix:<so>(Mu $) is pure {*}
src/core/Bool.pm:proto sub prefix:<!>(Mu $) is pure {*}
src/core/Bool.pm:proto sub prefix:<not>(Mu $) is pure {*}
src/core/Bool.pm:proto sub prefix:<?^>(Mu $) is pure {*}
src/core/Bool.pm:proto sub infix:<?&>(Mu $?, Mu $?) is pure {*}
src/core/Bool.pm:proto sub infix:<?|>(Mu $?, Mu $?) is pure {*}
src/core/Bool.pm:proto sub infix:<?^>(Mu $?, Mu $?) is pure {*}
src/core/Int.pm:proto sub chr($) is pure  {*}
src/core/Int.pm:sub is-prime(\x) is pure { x.is-prime }
src/core/Int.pm:proto sub expmod($, $, $) is pure  {*}
src/core/set_difference.pm:proto sub infix:<(-)>(|) is pure {*}
src/core/operators.pm:proto sub infix:<but>(|) is pure {*}
src/core/Any-iterable-methods.pm:proto sub infix:<min>(|) is pure {*}
src/core/Any-iterable-methods.pm:proto sub infix:<max>(|) is pure {*}
src/core/Any-iterable-methods.pm:proto sub infix:<minmax>(|) is pure {*}
src/core/set_multiply.pm:proto sub infix:<(.)>(|) is pure {*}
src/core/Numeric.pm:proto sub prefix:<+>($?) is pure {*}
src/core/Numeric.pm:proto sub prefix:<->($?) is pure {*}
src/core/Numeric.pm:proto sub abs($) is pure {*}
src/core/Numeric.pm:proto sub sign($) is pure {*}
src/core/Numeric.pm:proto sub log($, $?) is pure {*}
src/core/Numeric.pm:proto sub log10($, $?) is pure {*}
src/core/Numeric.pm:proto sub exp($, $?) is pure {*}
src/core/Numeric.pm:proto sub sin($) is pure {*}
src/core/Numeric.pm:proto sub asin($) is pure {*}
src/core/Numeric.pm:proto sub cos($) is pure {*}
src/core/Numeric.pm:proto sub acos($) is pure {*}
src/core/Numeric.pm:proto sub tan($) is pure {*}
src/core/Numeric.pm:proto sub atan($) is pure {*}
src/core/Numeric.pm:proto sub sec($) is pure {*}
src/core/Numeric.pm:proto sub asec($) is pure {*}
src/core/Numeric.pm:proto sub cosec($) is pure {*}
src/core/Numeric.pm:proto sub acosec(|) is pure {*}
src/core/Numeric.pm:proto sub cotan($) is pure {*}
src/core/Numeric.pm:proto sub acotan($) is pure {*}
src/core/Numeric.pm:proto sub sinh($) is pure {*}
src/core/Numeric.pm:proto sub asinh($) is pure {*}
src/core/Numeric.pm:proto sub cosh($) is pure {*}
src/core/Numeric.pm:proto sub acosh($) is pure {*}
src/core/Numeric.pm:proto sub tanh($) is pure {*}
src/core/Numeric.pm:proto sub atanh($) is pure {*}
src/core/Numeric.pm:proto sub sech($) is pure {*}
src/core/Numeric.pm:proto sub asech($) is pure {*}
src/core/Numeric.pm:proto sub cosech($) is pure {*}
src/core/Numeric.pm:proto sub acosech($) is pure {*}
src/core/Numeric.pm:proto sub cotanh($) is pure {*}
src/core/Numeric.pm:proto sub acotanh($) is pure {*}
src/core/Numeric.pm:proto sub sqrt($) is pure {*}
src/core/Numeric.pm:proto sub roots($, $) is pure {*}
src/core/Numeric.pm:proto sub floor($) is pure   {*}
src/core/Numeric.pm:proto sub ceiling($) is pure   {*}
src/core/Numeric.pm:proto sub round($, $?) is pure      {*}
src/core/Numeric.pm:proto sub infix:<+>(Mu $?, Mu $?) is pure   {*}
src/core/Numeric.pm:proto sub infix:<->(Mu $?, Mu $?) is pure   {*}
src/core/Numeric.pm:proto sub infix:<*>(Mu $?, Mu $?) is pure   {*}
src/core/Numeric.pm:sub infix:<×>(|c) is pure { infix:<*>(|c) }
src/core/Numeric.pm:proto sub infix:</>(Mu $?, Mu $?) is pure {*}
src/core/Numeric.pm:sub infix:<÷>(|c) is pure { infix:</>(|c) }
src/core/Numeric.pm:proto sub infix:<div>(Mu $?, Mu $?) is pure  {*}
src/core/Numeric.pm:proto sub infix:<%>(Mu $?, Mu $?) is pure   {*}
src/core/Numeric.pm:proto sub infix:<%%>(Mu $?, Mu $?) is pure  {*}
src/core/Numeric.pm:proto sub infix:<lcm>(Mu $?, Mu $?) is pure  {*}
src/core/Numeric.pm:proto sub infix:<gcd>(Mu $?, Mu $?) is pure {*}
src/core/Numeric.pm:proto sub infix:<**>(Mu $?, Mu $?) is pure  {*}
src/core/Numeric.pm:proto sub postfix:<ⁿ>(Mu $, Mu $) is pure  {*}
src/core/Numeric.pm:proto sub infix:«<=>»(Mu $, Mu $?) is pure {*}
src/core/Numeric.pm:proto sub infix:<==>(Mu $?, Mu $?) is pure {*}
src/core/Numeric.pm:proto sub infix:<!=>(Mu $?, Mu $?) is pure  {*}
src/core/Numeric.pm:proto sub infix:<≠> (Mu $?, Mu $?) is pure  {*}
src/core/Numeric.pm:proto sub infix:«<»(Mu $?, Mu $?) is pure   {*}
src/core/Numeric.pm:proto sub infix:«<=»(Mu $?, Mu $?) is pure {*}
src/core/Numeric.pm:proto sub infix:«≤» (Mu $?, Mu $?) is pure {*}
src/core/Numeric.pm:proto sub infix:«>»(Mu $?, Mu $?) is pure   {*}
src/core/Numeric.pm:proto sub infix:«>=»(Mu $?, Mu $?) is pure  {*}
src/core/Numeric.pm:proto sub infix:«≥» (Mu $?, Mu $?) is pure  {*}
src/core/Numeric.pm:proto sub infix:<+&>(Mu $?, Mu $?) is pure {*}
src/core/Numeric.pm:proto sub infix:<+|>(Mu $?, Mu $?) is pure {*}
src/core/Numeric.pm:proto sub infix:<+^>(Mu $?, Mu $?) is pure {*}
src/core/Numeric.pm:proto sub infix:«+<»(Mu $?, Mu $?) is pure {*}
src/core/Numeric.pm:proto sub infix:«+>»(Mu $?, Mu $?) is pure {*}
src/core/Numeric.pm:proto sub prefix:<+^>(Mu $) is pure {*}
src/core/Order.pm:proto sub infix:<cmp>(Mu $, Mu $) is pure {*}
src/core/Complex.pm:proto sub postfix:<i>(\a --> Complex:D) is pure {*}
src/core/set_union.pm:proto sub infix:<(|)>(|) is pure {*}
src/core/IO/Path.pm:    ) is pure {
src/core/IO/Path.pm:    my sub EXTENSION-SUBST ($ext, $base, $subst, $joiner) is pure {
src/core/set_intersection.pm:proto sub infix:<(&)>(|) is pure {*}
src/core/Junction.pm:proto sub any(|) is pure {*}
src/core/Junction.pm:proto sub all(|) is pure {*}
src/core/Junction.pm:proto sub one(|) is pure {*}
src/core/Junction.pm:proto sub none(|) is pure {*}
src/core/Junction.pm:sub infix:<|>(+values) is pure { values.any }
src/core/Junction.pm:sub infix:<&>(+values) is pure { values.all }
src/core/Junction.pm:sub infix:<^>(+values) is pure { values.one }
src/core/List.pm:proto sub infix:<,>(|) is pure {*}
src/core/List.pm:multi sub infix:<xx>(Mu \x, Int:D $n) is pure {
src/core/List.pm:proto sub infix:<X>(|) is pure {*}
src/core/List.pm:proto sub infix:<Z>(|) is pure {*}
src/core/set_symmetric_difference.pm:proto sub infix:<(^)>(|) is pure {*}
src/core/set_symmetric_difference.pm:multi sub infix:<(^)>(**@p) is pure {
src/core/set_precedes.pm:proto sub infix:<<(<+)>>($, $ --> Bool:D) is pure {
src/core/set_precedes.pm:only sub infix:<≼>($a, $b --> Bool:D) is pure {
src/core/set_precedes.pm:only sub infix:<<(>+)>>($a, $b --> Bool:D) is pure {
src/core/set_precedes.pm:only sub infix:<≽>($a, $b --> Bool:D) is pure {
src/core/set_proper_subset.pm:proto sub infix:<<(<)>>($, $ --> Bool:D) is pure {*}
src/core/set_proper_subset.pm:only sub infix:<⊄>($a, $b --> Bool:D) is pure {
src/core/set_proper_subset.pm:only sub infix:<⊃>($a, $b --> Bool:D) is pure {
src/core/set_proper_subset.pm:only sub infix:<⊅>($a, $b --> Bool:D) is pure {
src/core/Str.pm:proto sub infix:<unicmp>(|) is pure {*}
src/core/Real.pm:proto sub infix:<mod>($, $) is pure {*}
src/core/Any.pm:proto sub infix:<===>(Mu $?, Mu $?) is pure {*}
src/core/Any.pm:proto sub infix:<before>(Mu $?, Mu $?)  is pure {*}
src/core/Any.pm:proto sub infix:<after>(Mu $?, Mu $?) is pure {*}
src/core/Any.pm:proto sub item(|) is pure {*}
src/core/Mu.pm:proto sub defined(Mu) is pure {*}
src/core/Mu.pm:proto sub infix:<=:=>(Mu $?, Mu $?) is pure {*}
src/core/Mu.pm:proto sub infix:<eqv>(Any $?, Any $?) is pure {*}
src/core/Mu.pm:proto sub  infix:<−>(|)  is pure {*}
src/core/Mu.pm:proto sub prefix:<−>(|)  is pure {*}
JJ commented 6 years ago

Correct me if I'm wrong, but only if you call them with constants they will be evaluated in compile time. You can obviously call them with a variable, but their value will not be available in compile time, so they will act as "normal" variables. That's what I mean when I say a "real" use case. It could be a difficult computation performed in compile time, for instance, or pre-computing some calls. That is also why I say we would need a macro to have an use case as real as possible. I was thinking, for instance, about a problem we often find in evolutionary algorithms, royal road. It's computed with 4 bits, you can pretty much put it in a hash or else write a macro that generates its 16 cases, which then use the "is pure". Programatically it will not look like a sequence of 16 function calls, but a loop, and the 16 cases will be actually computed in compile time.

JJ commented 6 years ago

For instance, this thing:

sub four-bits() is pure {
    say "pure";
    return  <0 1> X~  <0 1> X~  <0 1> X~  <0 1>;
}

proto royal-road( Str $bits ) is pure {*}
multi royal-road( "0000" ) is pure { say "pure0000"; return 1; }
multi royal-road( "1111" ) is pure { say "pure1111"; return 1; }
multi royal-road( Str $bits ) is pure { say "purexxxx"; return 0; }

BEGIN { say ‘Begin’ }
say ‘Start’;
say royal-road("1010");
my %royal-road-hash = four-bits().map: { $^þ => royal-road( $^þ ) };

say %royal-road-hash;

four-bits() is called at compile time. However, royal-road is only called at compile time for the "1010" call. A macro could generate the code instead of the map. Anyway, this seems to be a good use case. Generating complicated data structures at compile time, saves you a bit every time it is run.