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

Make Fraction contexts as extensions so you can add fractions to other compatible contexts. #1108

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from

Conversation

dpvc
Copy link
Member

@dpvc dpvc commented Aug 29, 2024

This PR modifies the contextFraction.pl file to use the context-extension framework, making it possible to add fractions to any context. It still creates the original four fraction contexts, but also provides context::Fraction::extending() that can be used to add fractions to other contexts. E.g., you could use context::Fraction::extending("Matrix") to get a context in which fractions can be entered in matrices.

This should be backward compatible with the current contextFraction.pl from an author's perspective, except for the following:

  • The strictMultiplication and strictMinus flags have been removed, as they are no longer needed (the strict handling is done by extending the LimitedNumeric context, rather than reimplementing the strict behavior).
  • The handling of the internal information about integer values, negative values, and fractions has been modified. In the past, the object's class was modified to return INTEGER, MINUS and FRACTION, but now that fractions can be attached to arbitrary contexts, we don't want to modify the class of numbers, since that could affect how the other context operates. Instead these values are stored internally in private data within the typeRef objects.

It appears that at least one problem (Contrib/PCC/BasicAlgebra/LinearEquationApplications/Ratio20.pg) subclasses the LimitedFraction class and uses the original class values, so would need to be modified to work with the new contexts. I have renamed the original contextFraction.pl as legacyFraction.pl, for use in cases like this, though you all can decide if that is necessary or not.

Finally, I modified the t/contexts/fraction.t file to include the needed Parser classes for the new contextFractions.pl. Of course, that test file doesn't test most of the functionality, and needs many more tests to fully test the various options.

This PR includes the contextExtensions.pl file, even though I made a separate PR for it. Because that branch isn't in the openwebwork/pg repository, I can't target this PR to that branch. When that PR is merged, the contextExtensions.pl file should disappear from here.

@dpvc
Copy link
Member Author

dpvc commented Aug 29, 2024

PS, because this contextFraction.pl file is substantially different from the original, it may help to just view the file directly, rather than try to navigate the diff.

@dpvc dpvc added the Enhancement enhances the software label Sep 1, 2024
@drgrice1
Copy link
Sponsor Member

drgrice1 commented Sep 1, 2024

I am seeing an issue with the following MWE:

DOCUMENT();
loadMacros('PGstandard.pl', 'PGML.pl', 'contextFraction.pl', 'PGcourse.pl');

Context('Fraction');
$ans = Compute('x/7');

BEGIN_PGML
[`[$ans] =`] [_]{$ans}{10}
END_PGML
ENDDOCUMENT();

That works with the develop branch, but gives an error with this pull request.

@dpvc
Copy link
Member Author

dpvc commented Sep 1, 2024

OK, I forgot to include the cmp_defaults method. I was pretty sure I had tested that, but I guess I misremembered (probably remembered testing the units contexts instead). I've pushed the fix.

@drgrice1
Copy link
Sponsor Member

drgrice1 commented Sep 2, 2024

That fixes the issue with that example. Thanks.

I am seeing another issue when different contexts are used as in the following:

DOCUMENT();
loadMacros('PGstandard.pl', 'PGML.pl', 'contextFraction.pl', 'PGcourse.pl');
Context('Fraction');
$fractionAns = Compute('x/7');
Context('Numeric');
$numericAns = Compute("3");
BEGIN_PGML
[`[$fractionAns] =`] [_]{$fractionAns}{10}

[`[$numericAns] = `] [_]{$numericAns}{10}
END_PGML
ENDDOCUMENT();

@drgrice1
Copy link
Sponsor Member

drgrice1 commented Sep 2, 2024

Note that if either answer is removed from that problem it works. Also, if the fraction does not have x in it, then there is no problem.

@drgrice1
Copy link
Sponsor Member

drgrice1 commented Sep 2, 2024

You latest changes fix the issues! Thanks.

Copy link
Sponsor Member

@drgrice1 drgrice1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may not like the perltidy format, but look at much of the formatting of webwork and PG code before we adopted it. It was utterly horrendous. If you set your editor up with it, it also makes it easy to have nicely formatted code with no effort.

@dpvc
Copy link
Member Author

dpvc commented Sep 2, 2024

You latest changes fix the issues! Thanks.

Yes, I was still doing some more changes before writing up the details.

It turns out that changing the context was what was causing the problem. Although MathObjects contain a pointer to the context that created them, there are places where new objects are created via their new() or make() methods where they are passed the context, since the $self reference is the actual class name, not an instance, in those cases. That means those new() and make() calls don't have $self->context set to the context where the original object was created, but rather it defaults to the current context. Since the extension data is stored in the original object's context, if the context has changed, these new() and make() calls need to use the passed context in order to get the extension information. This wasn't taken into account in the context::Extensions::Super class, and so I had to include passing the context to these in some instances in order to get it to work when the context has been changed, as in your example.

I need to update the units PR to accommodate those changes as well.

You may not like the perltidy format

I don't object to it in principle, but I think some of the internal alignment make things harder to read, and some of the spacing is unnecessary. For example, just because two consecutive lines have equal signs or sufficient if statements doesn't mean those equal signs or ifs should be lined up. For example, this

	$x = $context->Package("Formula")->new($context, $x)->eval if !ref($x) && $x =~ m!/!;
	$x = $x->eval                                              if @_ == 0  && Value::classMatch($x, 'Fraction');

is nonsense in my opinion. It is much harder to see the second line as being a conditional when the if is so far away from the assignment. I also don't care for the extra spacing within braces, parens, and brackets for single-line expressions, like the spaces added in the brackets here, though I know that is a popular approach:

	my $class = join('::', (split(/::/, ref($self) || $self))[ 0, 1 ]);

That was the source of my commit comment. I frequently end up shaking my head at some of the changes it makes. But I understand the desire to have consistency, and you all can decide on the formatting you want to use. I may still grumble about it at times, however.

@drgrice1
Copy link
Sponsor Member

drgrice1 commented Sep 2, 2024

I also don't like some of the formatting at times. In some cases we may be able to improve things by tweaking the settings. At least you can do that, unlike prettier for JavaScript, typescript, etc. I just know that some of the webwork and PG code used to be really untradable with excessively inconsistent indentation in particular.

@drgrice1
Copy link
Sponsor Member

drgrice1 commented Sep 2, 2024

The particular alignment issue you mentioned is certainly not the nicest in appearance, I agree.

@dpvc
Copy link
Member Author

dpvc commented Sep 3, 2024

I agree with you about the consistency for indenting, line breaks, and continuation lines; that is a big win. It is the internal alignment that I find so unsettling. It seems to me that internal alignment should be used to indicate a meaningful structural connection between two lines, but most of the internal alignment that I see is meaningless, like the one I illustrated above (and I did choose it because of its particularly grievous nature, though most aren't that bad). That has several consequences. First, it makes things harder to read because the extra spacing interferes with reading the individual line, and one is incorrectly lead to believe there is a connection to a previous or following line, and it takes time to recognize that that is not the case. Second, because that is so frequently not the case, it trains you to ignore the alignment, as it is generally is meaningless. That means you have watered down the meaning of alignment, so that when it truly is important, you are less likely to recognize it among the sea of other nonsense alignments. For the most part, I think internal alignment should be relatively rare, and reserved for cases where a connection needs to be emphasized. I'm happy with alignment of => arrows in multi-line hash definitions, and even alignment of equal signs during the initialization of variables can be useful, but not every consecutive equal sign should be aligned with a previous one, and aligning && in the if statements like done in my example above is generally unnecessary and should only be done when the structure of the two really is connected. Similarly, aligning entries in two consecutive array literals is probably not called for except when the two arrays are connected in some way.

My own preference would be to have the authors do the internal alignment that is important (since they are the ones that understand which ones have meaning, whereas perltidy clearly doesn't), and have a style guide that indicates the ones that you expect to make, and request that authors abide by that before merging if they don't. By forcing such alignments, you may be prevention poor coders from making bad decisions, but you are also forcing other bad decisions, and preventing good coders from providing meaningful alignments (and actively undoing the meaningful ones they include). That seems to be to be guaranteeing a mediocre output; certainly not as bad as it was, but not as good as it could be.

Again, I realize it is up to the currently active programming community (of which I am not longer a member) to decide these issues, and I will abide by the choices that are made. But I reserve the right to make snarky commit comments now and again. :-)

@dpvc
Copy link
Member Author

dpvc commented Sep 3, 2024

I should point out that my recent changes to handle your cortex-switching example included a minor change to lib/Value.pm. It turns out that

my $symbol = shift || '';

would perform stringification on the first argument in order to do the || operation, and that caused a problem in the Value::isContext() function in some situations. So this has been updated to use // instead of ||. That should not be a controversial change.

@dpvc
Copy link
Member Author

dpvc commented Sep 3, 2024

PS, I meant to thank you for the careful testing. Very helpful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement enhances the software
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants