[Chaitana's Colossal Coaster] misleading expectations

Hi everyone,

I just completed the Chaitana’s Colossal Coaster exercise, and I found the expectations to be a bit misleading.

The exercise is used to introduce the concept of list methods, and some effort is given to teach the distinction between in-place methods such as <list>.sort() and functions that return a copy of the list such as sorted(<iterable>).

In the 6th task of the exercise, the student is asked to:

Define the sorted_names() function that takes 1 argument, queue , (the list of people standing in the queue), and returns a sorted copy of the list .

The task explicitely asks for the function to return a copy of the list. Yet, sorting the list in-place and returning that list will pass the test cases.

I think it might be confusing for new students, as the test cases do not point out the differences between copying a list or modifying it in-place.

This feeling is strengthened by the fact that tasks that intend to write methods that modify the list in-place (e.g. tasks 3 and 4) also require the methods to return the original list: it might not be perfectly clear to the student that the list has been modified in-place, and that it would be modified even without returning it.

I suggest one of the following:

  • Adding test cases that ensure that the functions either modify the lists in-place or not depending on the task expectations (that means that some past solution might not pass these new test cases anymore)
  • Modifying the exercise instructions so that each task is intended to modify the lists in-place (not mentioning copies anymore), and adding a global remark that even if these functions return the input lists, the original lists were modified in-place.

I’ll be happy to read your remarks and opinions on this! :wink:

1 Like

Agreed.

I provided a PR here to either set better expectations or update the test. Was not aware of the required discussions, but glad I’m not alone in the assessment the instructions are a tad misleading.

I saw three of “the same” PR’s come in, and linked them in the latest one (linked in your message) so hopefully it reduces confusion.

1 Like

Yeah idk why that one was sent twice. Thanks for the heads up!

However, the third is a separate PR for a separate issue, so it does not supersede the other two.

Consequentially, I clarified that comment/aspect in the ‘third PR’ and added the supersession note to the ‘second PR’ (the only one actually superseding).

1 Like

Hi @leonqadirie :wave:t4:

Thanks for submitting this. If students want to use sorted(), and understand what it does (returns a copy of said sequence), they are free to do so. And we do explain it in the introduction. It has the exact same effect as manually making a copy and then mutating that copy. Either is fine.

sorted() may not technically be a list method, but it applies to sequences, which is a meta-typ of str, tuple, Array.array, list, and bytearray (and more I am leaving out).

So I think we leave the instructions as-is, and take up @Gautzilla second point: being very clear about testing for when and not when the original list is mutated.

I am going to think on it a while. Generally in testing is is a BIG no-no to test for implementation as opposed to result. Implementations change, and (with specific exception) should not be pertinent to the caller or the outcome.

Since this is an exercise that strives to teach a student the difference between mutating a list and not doing so … well, as I said I will think on it. This exercise has had quite a few changes in the last little while. It may be time to revise things altogether.

1 Like

Yes, I did notice it was different, but no discussion to know why, but I think you have sorted the communication out.

Bringing discussions here first can help avoid that kind of thing from happening in the first place, making the issues and pull requests be things that are confirmed to be worked on rather than having a lot of potential “noise” there.

1 Like

Hi @BethanyG - first, thank you for your explanation and I’m really sorry for the PR noise.

Generally in testing is is a BIG no-no to test for implementation as opposed to result . Implementations change, and (with specific exception ) should not be pertinent to the caller or the outcome.

I agree on not testing for implementation details, just for my understanding: Is the fact that a function might mutate its input or not an implementation detail? My intuition suggests this is a critical part of the function’s interface; but I’m also somewhat new to non-FP.

To be sure, I do not suggest prohibiting sorted().

Either way you decide on how to proceed, thanks for consideration!


Yes, I did notice it was different, but no discussion to know why, but I think you have sorted the communication out.

I’m genuinely interested in tips on how I could have delineated the third PR from the first two better, as they had differing titles, descriptions, and linked forum discussions. Maybe the titles had a too big overlap or I should have edited the initial description with the linked forum discussions instead of writing a new comment beneath the GitHub-actions bot’s remarks?

Bringing discussions here first can help avoid that kind of thing from happening in the first place, making the issues and pull requests be things that are confirmed to be worked on rather than having a lot of potential “noise” there.

Yes; I hadn’t understood from the README that non-features/new exercises require a prior discussion in this forum but definitely understand the value of this approach and will do in the future. :slight_smile:

2 Likes

TL;DR: Here? Yes.

I think the “knee-jerk” Pythonista answer would be that if I really didn’t want a list mutated, I would pass a copy of the data as the caller, since a list is a mutable data structure.

But copies are also expensive, so I wouldn’t just blindly or automatically copy any and all lists calling or called (or dicts or any other mutable type). I’d weigh if the context warranted copies to avoid unintentional mutation because of some constraint, complexity, or obscurity with the program.

But I shouldn’t have any sort of expectation for immutability with a list. If I am passing a list to a function, I have to assume that the function will operate on the list (and it is often desirable it does so) — otherwise why use one? tuples are sequences that are immutable, so they would be a better choice if immutability is truly needed. They are also faster and smaller in memory because they don’t have to support mutable sequence operations.

The majority of list methods always mutate the list (and return None, pop() being an exception), so the instructions for this exercise are less an interface directive and more a reminder of what most of the methods will actually do.

And that may be the problem with the instructions, because we keep having folx come along and complain about the addition or absence of “original list”/“copy of the list” comments. And making copies vs not making copies of data is a much bigger and much more nuanced conversation for much later in a Python learner’s journey. It is also one that is much better done by a mentor.

Python’s ethos is very much “try to understand intent, and act accordingly”. If someone is unclear in their intent, accidents can happen. Conversely, if someone decides they really don’t want to respect intent, they can (usually easily) circumvent it.

So all of this is my (very) long winded way of saying that I am somewhat uncomfortable with validating any of the queues. I’ve sort of been backed into doing so by multiple students and mentors complaining that to do otherwise is to create confusion. I am also somewhat convinced that helping students understand the pitfalls of unintentionally mutating a list at this stage in their journey is a good thing.

But it also feels like the copy/not copy discussion is loading a awfully lot onto an exercise that is about practicing the use of pop(), insert(), append(), index(), remove(), etc.

I don’t actually care if students operate on an original or a copy (with the exception of sort() vs sorted()) - just that they practice list methods and clearly understand that whatever they operate on will be mutated in most circumstances.

How to convey that with instructions and tests is an ongoing struggle.

Does that make sense?

Phew! That was a lot. Thanks for reading. :sweat_smile:

2 Likes

Phew! That was a lot. Thanks for reading. :sweat_smile:

Haha it was, but it was great, thank you!

So my key takeaway as an admittedly mutability-careful person is:
A caller with mutable data structures should not trust any functions or methods it passes them to to not mutate it.

I am also somewhat convinced that helping students understand the pitfalls of unintentionally mutating a list at this stage in their journey is a good thing.

Fair enough. The options seem to be:

  1. Keep everything as-is with the instructions asking for a copy (as it is part of the instructions, this will be read as a directive by some) but the exercise not testing against it. Easiest for those not (yet) bothered to notice or care. Those who do learn a bit about the nuance but might occasionally bother complaining about the ambiguity of it.
  2. Scratch the ‘copy’ part of the exercise, reducing the complains, but also not teaching any nuances of unintentionally mutating a list.
  3. Test against the ‘implementation detail’ the instruction asks for, kind of violating the testing principle.
  4. Change the task (or add one) to (also?) return a mutated variant and test accordingly. This might teach the nuances the best, but loads a tad bit more onto the exercise. In this case I’d argue didactic trumps testing principles.
  5. Scratch the whole task and optionally enjoy a cup of tea and not be bothered too much in the future. Doesn’t teach any mutability nuances.

While personally I’d tend towards option 2 or 4, I don’t have all the necessary context and sympathise with the conundrum.

In any case, I have learned the key takeaway above and am grateful and a bit more paranoid for that :D

PS: Not expecting any response, thanks for your time!