Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate a lazy division?

I want to generate the sequence of

1, 1/2, 1/3, 1/4 ... *

using functional programming approach in raku, in my head it's should be look like:

(1,{1/$_} ...*)[0..5]

but the the output is: 1,1,1,1,1 The idea is simple, but seems enough powerful for me to using to generate for other complex list and work with it.

Others things that i tried are using a lazy list to call inside other lazy list, it doesn't work either, because the output is a repetitive sequence: 1, 0.5, 1, 0.5 ...

my list = 0 ... *;
(1, {1/@list[$_]} ...*)[0..5]
like image 583
metagib Avatar asked Oct 31 '20 21:10

metagib


2 Answers

See @wamba's wonderful answer for solutions to the question in your title. They showcase a wide range of applicable Raku constructs.

This answer focuses on Raku's sequence operator (...), and the details in the body of your question, explaining what went wrong in your attempts, and explaining some working sequences.

TL;DR

The value of the Nth term is 1 / N.

# Generator ignoring prior terms, incrementing an N stored in the generator:
{ 1 / ++$ } ... *                               # idiomatic
{ state $N; $N++; 1 / $N } ... *                # longhand

# Generator extracting denominator from prior term and adding 1 to get N:
1/1, 1/2, 1/3, 1/(*.denominator+1) ... *        # idiomatic (@jjmerelo++)
1/1, 1/2, 1/3, {1/(.denominator+1)} ... *       # longhand (@user0721090601++)

What's wrong with {1/$_}?

1, 1/2, 1/3, 1/4 ... *

What is the value of the Nth term? It's 1/N.

1, {1/$_} ...*

What is the value of the Nth term? It's 1/$_.

$_ is a generic parameter/argument/operand analogous to the English pronoun "it".

Is it set to N?

No.

So your generator (lambda/function) doesn't encode the sequence you're trying to reproduce.

What is $_ set to?

Within a function, $_ is bound either to (Any), or to an argument passed to the function.

If a function explicitly specifies its parameters (a "parameter" specifies an argument that a function expects to receive; this is distinct from the argument that a function actually ends up getting for any given call), then $_ is bound, or not bound, per that specification.

If a function does not explicitly specify its parameters -- and yours doesn't -- then $_ is bound to the argument, if any, that is passed as part of the call of the function.

For a generator function, any value(s) passed as arguments are values of preceding terms in the sequence.

Given that your generator doesn't explicitly specify its parameters, the immediately prior term, if any, is passed and bound to $_.

In the first call of your generator, when 1/$_ gets evaluated, the $_ is bound to the 1 from the first term. So the second term is 1/1, i.e. 1.

Thus the second call, producing the third term, has the same result. So you get an infinite sequence of 1s.

What's wrong with {1/@list[$_+1]}?

For your last example you presumably meant:

my @list = 0 ... *;
(1, {1/@list[$_+1]} ...*)[0..5]

In this case the first call of the generator returns 1/@list[1+1] which is 1/2 (0.5).

So the second call is 1/@list[0.5+1]. This specifies a fractional index into @list, asking for the 1.5th element. Indexes into standard Positionals are rounded down to the nearest integer. So 1.5 is rounded down to 1. And @list[1] evaluates to 1. So the value returned by the second call of the generator is back to 1.

Thus the sequence alternates between 1 and 0.5.

What arguments are passed to a generator?

Raku passes the value of zero or more prior terms in the sequence as the arguments to the generator.

How many? Well, a generator is an ordinary Raku lambda/function. Raku uses the implicit or explicit declaration of parameters to determine how many arguments to pass.

For example, in:

{42} ... * # 42 42 42 ...

the lambda doesn't declare what parameters it has. For such functions Raku presumes a signature including $_?, and thus passes the prior term, if any. (The above lambda ignores it.)

Which arguments do you need/want your generator to be passed?

One could argue that, for the sequence you're aiming to generate, you don't need/want to pass any of the prior terms. Because, arguably, none of them really matter.

From this perspective all that matters is that the Nth term computes 1/N. That is, its value is independent of the values of prior terms and just dependent on counting the number of calls.

State solutions such as {1/++$}

One way to compute this is something like:

{ state $N; $N++; 1/$N } ... *

The lambda ignores the previous term. The net result is just the desired 1 1/2 1/3 ....

(Except that you'll have to fiddle with the stringification because by default it'll use gist which will turn the 1/3 into 0.333333 or similar.)

Or, more succinctly/idiomatically:

{ 1 / ++$ } ... *

(An anonymous $ in a statement/expression is a simultaneous declaration and use of an anonymous state scalar variable.)

Solutions using the prior term

As @user0721090601++ notes in a comment below, one can write a generator that makes use of the prior value:

1/1, 1/2, 1/3, {1/(.denominator+1)} ... *

For a generator that doesn't explicitly specify its parameters, Raku passes the value of the prior term in the sequence as the argument, binding it to the "it" argument $_.

And given that there's no explicit invocant for .denominator, Raku presumes you mean to call the method on $_.


As @jjmerelo++ notes, an idiomatic way to express many lambdas is to use the explicit pronoun "whatever" instead of "it" (implicit or explicit) to form a WhateverCode lambda:

1/1, 1/2, 1/3, 1/(*.denominator+1) ... *

You drop the braces for this form, which is one of its advantages. (You can also use multiple "whatevers" in a single expression rather than just one "it", another part of this construct's charm.)

This construct typically takes some getting used to; perhaps the biggest hurdle is that a * must be combined with a "WhateverCodeable" operator/function for it to form a WhateverCode lambda.

like image 173
raiph Avatar answered Oct 19 '22 17:10

raiph


TIMTOWTDI

routine map

(1..*).map: 1/*

List repetition operator xx

1/++$ xx *

The cross metaoperator, X or the zip metaoperator Z

1 X/ 1..*
1 xx * Z/ 1..*

(Control flow) control flow gather take

gather for 1..* { take 1/$_ }

(Seq) method from-loop

Seq.from-loop: { 1/++$ }

(Operators) infix ...

1, 1/(1+1/*) ... *
{1/++$} ... *
like image 14
wamba Avatar answered Oct 19 '22 16:10

wamba