[HN Gopher] Finding and fixing standard misconceptions about pro...
       ___________________________________________________________________
        
       Finding and fixing standard misconceptions about program behavior
        
       Author : vector_spaces
       Score  : 60 points
       Date   : 2024-04-12 16:09 UTC (2 days ago)
        
 (HTM) web link (blog.brownplt.org)
 (TXT) w3m dump (blog.brownplt.org)
        
       | justin_oaks wrote:
       | This is great. It's good to see someone attempting to identify a
       | mistaken mental model and then correcting it.
       | 
       | In both formal and informal education, it is rare to see
       | teachers/parents/mentors identifying misunderstandings and
       | correcting them. All too often the one teaching reiterates the
       | lesson material instead of asking the learner about their
       | understanding and correcting misunderstandings.
        
         | 082349872349872 wrote:
         | I self-taught basic algebra with a "choose your own adventure"
         | book that advanced to the next topic for correct answers and
         | went through pages explaining the common misconceptions then
         | returned to the question for incorrect.
         | 
         | (to be fair, class sizes were ~30 when was in school, so asking
         | the learner about their understanding and correcting
         | misunderstandings would not have been likely: assuming 40
         | minutes lecture time for a 50 minute class, that's 20 seconds
         | per student, and at most ~2 minutes if no lecture time
         | whatsoever)
        
       | Turing_Machine wrote:
       | Nice, but needs some more attention to variant (but equivalent)
       | responses.
       | 
       | For example, for one of the questions in the first module I
       | selected "Other", then typed "Error" (in the free-form response
       | box). The answer it was looking for was "error" (lower-case e).
       | 
       | Either it should accept both, or if you're really insistent on
       | distinguishing the two, it should be made more clear at the
       | beginning that system messages (rather than explicit results) are
       | going to be case-sensitive. Counting "ABC" wrong when the
       | expected answer is "abc" would be fair game. Counting "Error"
       | wrong for a pseudo-language that hasn't even been formally
       | defined is not, or so I see it.
       | 
       | Looking at some of the languages on which this is claims to be
       | based:
       | 
       | In JavaScript, typing abc when it's undefined produces: Uncaught
       | ReferenceError: abc is not defined at <anonymous>:1:1 (anonymous)
       | @ VM33:1
       | 
       | (note capital E in ReferenceError)
       | 
       | In Python3 you get:
       | 
       | NameError: name 'abc' is not defined. Did you mean: 'abs'?
       | 
       | (again, note capital E in NameError)
       | 
       | In Racket 8.9 you get:
       | 
       | abc: undefined; cannot reference an identifier before its
       | definition
       | 
       | (no 'error' of any kind!)
       | 
       | I didn't install OCaml, because I need another "package manager"
       | like I need a hole in the head.
       | 
       | Java: does not compile, although to be fair the compiler error
       | message does have an 'error with lower-case e' in it.
       | 
       | C#: Yeah, not gonna install that either.
       | 
       | This may seem extremely nit-picky (and it is), but when you're
       | teaching people who've never programmed before, and who may have
       | never even encountered the concept of case sensitivity before
       | ("But Google doesn't care even if I type in all caps!"), nit-
       | picky is the way it needs to be.
       | 
       | As I said, I do like this, but it needs more attention paid to
       | parsing free-form responses. The problem is not unique to this
       | system, of course. The free-form response is where most systems
       | of this general type tend to fail.
        
         | echoangle wrote:
         | There was an example directly before that question which shows
         | an example output when dividing by zero. There you can see what
         | format they expect the error to be.
        
           | Turing_Machine wrote:
           | These are supposed to be beginning students. You can't just
           | assume that they know that there's even any difference
           | between "Error" and "error".
        
         | soegaard wrote:
         | > In Racket 8.9 you get:         > abc: undefined; cannot
         | reference an identifier before its definition         > (no
         | 'error' of any kind!)
         | 
         | But that is an error.
         | 
         | If you try this in the terminal, you will see that the message
         | is colored red to show it is an error.
         | 
         | If you try it in DrRacket, you will see the message written in
         | red text with icons leading to the stack trace.
         | 
         | If you try it in Emacs, you will see the message in red
         | indicating it is an error.
        
       | echoangle wrote:
       | I get that the syntax options are just to adapt to different
       | language users but it's a little bit confusing that some claims
       | are actually wrong in the specific language. In the first test,
       | they state that adding two bools gives an error, even though this
       | is perfectly valid in Python.
        
       | Animats wrote:
       | > We also believe that the terms "call-by-value" and "call-by-
       | reference" are so hopelessly muddled at this point (between
       | students, instructors, blogs, the Web...) that finding better
       | terminology overall would be helpful.
       | 
       | Maybe that shouldn't even be exposed to the programmer. The
       | programmer-level questions are, is it copyable, is it mutable,
       | and is it alias-free? Whether it's passed as a copy or a pointer
       | is really an issue for the compiler. If you're passed a read-only
       | copy of something guaranteed to not be aliased, you can't tell
       | the difference from a reference. Some Modula compilers made that
       | decision automatically, based on object size.
       | 
       | Rust compilers have the info to do this. I'm sometimes asking
       | myself whether I should pass, say, an array of 3 32-bit floats in
       | graphics code by reference or by value. The compiler knows better
       | than the programmer what the hardware can copy fast, and that may
       | differ with the platform.
        
         | erik_seaberg wrote:
         | Deep value equality can also be expensive to check, so some
         | languages default to reference equality even for immutable
         | values.
        
           | mrkeen wrote:
           | I used to think having both == and .equals in Java was
           | specifically dumb, but the more I think about it, the more I
           | dislike this kind of sneaky reference equality in the general
           | case.
           | 
           | Comparing references isn't even an optimised version of
           | comparing values, because when the references don't match but
           | the values do, equality will be false.
           | 
           | I struggle to think of when reference equality would be
           | useful to me. Like if I were writing a standard for-loop over
           | an array, it's like I'd be comparing 'i' to 'i'.
           | 
           | If == gives me a fast 'yes' response, I feel like I (or my
           | algorithm) should have already known they were going to be
           | the same.
           | 
           | If == gives me a fast 'no' response, then do I not actually
           | care about the value?
        
         | nox101 wrote:
         | > Maybe that shouldn't even be exposed to the programmer. The
         | programmer-level questions are, is it copyable, is it mutable,
         | and is it alias-free? Whether it's passed as a copy or a
         | pointer is really an issue for the compiler.
         | 
         | The problem here is the definition of "call by reference". In
         | C++ that means being able to change the value outside of the
         | function taking the reference.                   void
         | setByReference(float& v) { v = 123; }              float v;
         | setByReference(v);         cout << v;          // prints 123
         | 
         | That feature of being able to pass by reference doesn't exist
         | in say, JavaScript. you can only pass by value in JavaScript.
         | The types of values in JS are undefined, null, boolean, number,
         | string, reference-to-function, reference-to-object. You can
         | never pass anything by reference, only by value.
         | 
         | And that's where it gets confusing. If you have a variable
         | who's value is a reference-to-object you pass the value. The
         | value being "reference-to-object"
         | 
         | To re-iterate                   const n = 1;    fn(n);  // call
         | by value, type of value = number         const s = 'abc' fn(s);
         | // call by value, type of value = string         const o = {}
         | fn(o);  // call by value, type of value = reference-to-object
         | 
         | In C++ though, you pass the a reference to the variable itself
         | (in the example above). That's called call-by-reference.
         | 
         | I can see why the OP feels it's muddled.
        
           | mattnewport wrote:
           | Yeah, this is one of those things that while it may be more
           | technically correct causes a lot of unnecessary confusion. I
           | remember being confused by this as a C++ programmer learning
           | Java when resources claimed that Java was always pass by
           | value where the actual behaviour in almost all cases (due to
           | Java going almost all-in on objects) is what a C++ programmer
           | would expect from pass by reference.
           | 
           | I still see even relatively experienced programmers who don't
           | understand this, particularly working with Unity where a lot
           | of programmers came from C++ to C# and don't realise for
           | example that a C# function that takes a List 'by value' and
           | modifies it is actually modifying the same instance that was
           | passed in by the caller.
        
         | kmoser wrote:
         | > The way we _informally_ talk about programming concepts (like
         | "pass a variable"), and the syntactic choices our languages
         | make (like return x), are almost certainly also sources of
         | confusion. The former can naturally lead students to believe
         | variables are being aliased, and the latter can lead them to
         | believe the variable, rather than its value, is being returned.
         | 
         | There's the problem right there: any _informal_ talk about
         | programming concepts is bound to lead to confusion because
         | informal language is open to interpretation (no pun intended).
         | That 's why we have formal definitions for these terms, as well
         | as language specs which define how pass-by-value and pass-by-
         | reference (if applicable) and pass-by-address (if applicable)
         | work in a given language.
        
         | initplus wrote:
         | The problem is that the existing language concepts that
         | implement these ideas are ambiguous in many languages. Does int
         | *x imply that x is an optional value, or that it's intended to
         | be shared with/from others, or both of the above?
        
         | assbuttbuttass wrote:
         | > The compiler knows better than the programmer what the
         | hardware can copy fast
         | 
         | This is true, but aliasing makes it extremely difficult to
         | automatically decide between reference and copy. For example,
         | suppose you have a function that takes your array, and also a
         | mutable slice:                 fn f(a: [float32; 3], out: &mut
         | [float32])
         | 
         | And now you call it with the same argument twice:
         | let a = [1., 2., 3.];         f(a, &mut a)
         | 
         | The compiler cannot choose to pass a by reference here, since
         | that would create simultaneous immutable and mutable
         | references, which is forbidden by rust semantics
        
       ___________________________________________________________________
       (page generated 2024-04-14 23:01 UTC)