Live data from Hacker News

I Want Off Mr. Golang's Wild Ride

fasterthanli.me

471–480 of 508 posts

Re: I Want Off Mr. Golang's Wild Ride

#471
post #250

Earlier quoted context omitted.

It will not compile. (As I said earlier, you can always fallback to a panic aka "I don't wanna deal with the error so let my program crash", but an error will not silently propagate through the stack)

That's is completely false, stop spreading misinformation. This code will compile (see https://play.rust-lang.org/?version=stable&mode=debug&editio... ): pub fn foo() -> Result { Err(1) } pub fn bar() -> Result { foo(); Ok(()) } It will provide a warning, but there's a ton of stuff in c++ that would throw a warning and you wouldn't say that it "will not compile". A trivial change that still doesn't handle the error w…

I wonder if people upthread meant the warning, or e.g. getting the `Foo` in `Result`.

(EDIT: nevermind, just looked again and pcwalton was referring to the warning and specifically `Result`; oh well)

Because the latter is impossible in Rust and probably more relevant to the usual cited issue with Go's pair approach (i.e. using the null/zeroed `Foo` without checking if there was an error).

I do agree though that the warning isn't to stop you from not handling the error at all, it's more of a hint that maybe you forgot something.

Printing a `Result` may be the legitimate way to handle it in that case, it's largely left to the user to decide what propagates and what doesn't.

Re: I Want Off Mr. Golang's Wild Ride

#472
post #425

Classic: “There are only two kinds of languages: the ones people complain about and the ones nobody uses.”[1] The thing that bugs me is the comparison to Rust. I mean, the author did caveat that he chose it because Rust provided the best available counter examples to his specific gripes. But my issue is that comparison seems to make a false conclusion: Rust is better. My intuition says if the author used Rust (or any…

>There are only two kinds of languages: the ones people complain about and the ones nobody uses Repeating this quote again and again won't make it correct.

The third kind is people use little bit and circlejerk that it solves world's problems, ivory tower elitism.

Re: I Want Off Mr. Golang's Wild Ride

#473
post #466

Earlier quoted context omitted.

heh. someone hasnt used a monad.

There is no way to avoid leaking implementation details, it's just a matter of how much you leak. Even monads leak their performance characteristics.

*sometimes and not always

State, for example, doesn't leak. You never worry about the fact that it's a function `s -> (s, a)` and it gets optimized away like nuts.

Re: I Want Off Mr. Golang's Wild Ride

#474
post #434

Earlier quoted context omitted.

The blame Java gets for checked exceptions its unfair. Firstly, CLU and C++ were there first, so the language designers were building on something that they though was a trend that would carry on. Most never having learned how checked exceptions were done in CLU and C++, blame Java for them. Then even though it is more convenient to work without them, I do miss in other languages, because developers hate documentatio…

I mean personally I happen to agree that Java's checked exceptions represent a reasonable experiment, although I think they suffer from low-level problems, namely the way they interact with lambdas, the way they're at odds with the community's interface-happy habits, and the self-inflicted tension between whether something should be checked or unchecked (should an out of bounds exception on a dynamically sized array…

There are many Java developers who like checked exceptions. True, there are issues about polymorphizing them (which is what you feel when you use them with lambdas), but they're solvable. As to file vs. array, I do see a fundamental difference. Whether the index is out of bounds or not is up to the program; whether the file exists is not.

Re: I Want Off Mr. Golang's Wild Ride

#475
post #90

Earlier quoted context omitted.

Is there even a single go abstraction that doesn't leak it's guts everywhere?

There aren't any abstractions in any language or library that don't leak everything about what they are trying to hide as well as everything about their own implementation. That's just life. It's impossible to hide complexity. Whatever wraps one thing will be strictly more complex than the wrapped thing was.

"It's impossible to reach absolute zero, so it's also impossible to drink chilled beverages."

Re: I Want Off Mr. Golang's Wild Ride

#476
post #452

Earlier quoted context omitted.

I disagree that those are doing the same thing. I propose that the actual answer is c: use std::cell::RefCell; fn mystery_c() -> i32 { let x = RefCell::new(1); let capture = || x.borrow(); x.replace(2); return *x.borrow() * *capture(); } fn main() { println!("{}", mystery_c()) } Which is what I meant when I said that Box might be the analogous thing (I guess it's actually RefCell, whoops!). And note that in this case…

You lost me, boss! Why do you think mystery_c is closer to mystery_py than mystery_b?

Let's go on a journey.

The answer is that I started with a hunch. You're treating x as a pointer sometimes, and a value other times. That seems strange, and unlike the python. In python the thing is always access the same way, it isn't a ptr type sometimes and a value type others.

So first let's talk about scopes. In python, you aren't introducing a closure. If we do introduce a closure, like with an IIFE:

    def mystery_closure():
        x = 1
        closure = (lambda v: lambda: v)(x)
        x = 2
        return x * closure()
suddenly we get 2. The IIFE/outer closure here is equivalent to the capture happening in rust. So this is more equivalent to the rust examples than your python example. Closures are what matter, not variable mutability.

Cool, so now let's add another wrinkle: `i32` in rust isn't a mutable type, there are no mutating methods on an i32. What happens if we use a type that has mutating methods, like a vec?

Let's start in python, since python doesn't allow multiline lambdas, we have to swap to using an inner function, which is fine, this makes the structure a bit clearer in python.

    def mystery_ mutable():
      x = [1]
      def closure():
        def inner(v):
          v.append(2)
          return v
        return inner(x)
      x.append(3)
      return x + closure()
And what if we do the same in rust? Well, we have to mark x as a mutable ref:

    fn mystery_b() -> Vec {
        let mut x = vec![1];
        let ptr = &mut x as *mut Vec;
        let capture = || unsafe{ (*ptr).push(2); 
                                  ptr };
        x.push(3);
        unsafe { x.extend(capture().as_ref().unwrap().iter()); }
        return x
      }
So the python value is a mutable ref, right? Well no, we're back to the whole issue of the closure being able to modify things outside itself in rust with a mut ref that we can't do with python:

    def mystery_mutable():
      x = [1]
      def closure():
        def inner(v):
          v = [5]
          v.append(2)
          return v
        return inner(x)
      x.append(3)
      return x + closure()
This returns [1,3,5,2] in python. If you translate it to rust with a mutable ref pattern, you'll get [5,2,5,2] and the 3 will just disappear:

    fn mystery_mutable() -> Vec {
        let mut x = vec![1];
        let ptr = &mut x as *mut Vec;
        let capture = || unsafe{ (*ptr) = vec![5,2];
                                  ptr };
        x.push(3);
        unsafe { x.extend(capture().as_ref().unwrap().iter()); }
        return x
      }
So in python, the thing isn't a const ref, but it's not a mutable ref, either, and it's certainly not a value type.

In languages like rust and cpp we describe calls as pass by reference or pass by value. Pass by value is mostly irrelevant here. When passing by reference, you can use a mutable or immutable reference. Immutable references don't allow you to modify the object, just read it. Mutable references allow you to modify or replace the object. With normal pointers and references, if you're able to modify the referenced object you can also replace it with an entirely new object.

The reasons for this are tricky, but have to do with self references in methods (self/this has to be mutable for a mutable method to work). In rust and cpp the self reference is exposed, so you can make it point elsewhere. In python you can't do this. This means that its tricky to pass an immutable reference to a mutable object in rust/cpp, but in python this is the only way things get passed around.

Rust calls this "interior mutability", and RefCell is the way to do interior mutability with references, as opposed to copyable types. The docs for RefCell actually call out passing &self to a method that requires mutability[1] as a use for RefCell, so in general you could use the RefCell to implement a python-like set of containers that can be passed "immutably" and still modified internally. In Pseudo-rust:

    struct PyVec {
      backing_arr: RefCell>
    }

    impl PyVec {
      fn push(&self, v: T) {  // This isn't mutable?!
        backing_arr.borrow_mut().push(v);
      }
      ...
    }
Which would match python's semantics very closely

[1]: https://doc.rust-lang.org/beta/std/cell/index.html#implement...

Re: I Want Off Mr. Golang's Wild Ride

#477

Earlier quoted context omitted.

And the other one is the tendency for Go's design to say "exceptions are allowed for me but not for thee". Yes. Exceptions are kind of a pain, but the workarounds for not having them are worse. Passing back "result" types tends to lose the details of the problem before they are handled. Rust is on, what, their third error handling framework? Exceptions have a bad reputation because C++ and Java botched them. You need…

> Resource Acquisition Is Initialization is fine It doesn't work well at all for transactions, where both A and B must succeed or neither. https://dlang.org/articles/exception-safe.html

Thanks, that's a nice discussion!

Do you know if a similar, more granular approach (scope(exit)=~finally, scope(failure)=~catch, scope(success)=else) over go-style defer=~finally is implemented elsewhere than D?

Re: I Want Off Mr. Golang's Wild Ride

#478
post #452

Earlier quoted context omitted.

You lost me, boss! Why do you think mystery_c is closer to mystery_py than mystery_b?

Let's go on a journey. The answer is that I started with a hunch. You're treating x as a pointer sometimes, and a value other times. That seems strange, and unlike the python. In python the thing is always access the same way, it isn't a ptr type sometimes and a value type others. So first let's talk about scopes. In python, you aren't introducing a closure. If we do introduce a closure, like with an IIFE: def myster…

> The IIFE/outer closure here is equivalent to the capture happening in rust. So this is more equivalent to the rust examples than your python example.

Wait, I don't follow. Rewriting your example to only use one lambda for clarity, we have:

    def mystery_closure_one_lambda():
        x = 1
        def capture(v):
            return lambda: v
        closure = capture(x)
        x = 2
        return x * closure()
        
So notice the lambda (i.e. what we are assigning to the variable 'closure') is now capturing v, not x, which is why it doesn't see the change we make to x, i.e., why it returns 2 instead of 4.

But this is not equivalent to the rust code! There is no v at all in rust. We are capturing x! (It's slightly obscured by the fact that we to use an unsafe ptr to defeat the borrow checker, but we are still capturing x.)

So I do not think mystery_closure is equivalent to either of the rust mystery_a or mystery_b above; it is in fact equivalent to this:

  fn mystery_closure() -> i32 {
      let mut x = 1;
      let closure = (|v| move || v)(x);
      x = 2;
      x * closure() 
  }
Which also returns 2, just like the python code. (It's also a direct translation of the python code!)

> Let's start in python, since python doesn't allow multiline lambdas, we have to swap to using an inner function, which is fine, this makes the structure a bit clearer in python.

Careful! -- your de-lamba-fication accidentally changed the semantics. If we just de-lambda-fy, we get:

  def mystery_int():
      x = 1
      def closure():
          def inner(v):
              return v
          return inner(x)
      x = 2
      return x + closure()
Which returns 4, showing it's defnly not equivalent. The correct de-lambda-ficiation is:

    def mystery_closure_no_lambas():
        x = 1
        def capture(v):
            def inner():
                return v
            return inner
        closure = capture(x)
        x = 2
        return x * closure()
(which as a sanity check, returns 2, as it should).

Bringing in mutable reference data types like vec is I think not really relevent to what's at play here.

In both rust and python, the non-reference types mut i32 (rust) and int (python) are mutable. In rust you can pass a mutable reference to an i32, and in python you can't, but so what; that's not really relevent.

DIGRESSION:

Just for funsies, you actually can achieve what are essentially mutable references in python3 (you could also do this in py2 if you wanted to get nasty with locals()):

  # a mutable reference to a local variable
  class Ref:
      def __init__(self, getfn, setfn):
          self.getfn, self.setfn = getfn, setfn
      def get(self): return self.getfn()
      def set(self, v): self.setfn(v)
      value = property(get, set, None, "reference to local variable")

  # change a local variable using the mutable refernce
  def mutate(ref, new_value):
      ref.value = new_value

  def mystery_py_mutable_ref():
      x = 1

      # get a mutable reference 'ref' to x
      def get(): 
          return x
      def set(v):
          nonlocal x
          x = v
      ref = Ref(get, set)

      # capture x in a closure
      capture = lambda: x

      # mutate x
      mutate(ref, 2)

      # finally evaluate x and the closure; this will return 4!
      return x * capture()
END DIGRESSION

But anyway, I don't think it's actually relevent here.

Question: Are you familiar with scheme? Would you agree or disagree that the following scm_mystery_a and scm_mystery_b are equivalent to the rust mystery_a and mystery_b functions?

  (define scm_mystery_a 
      (lambda ()
          (let ((x 1))
          (let ((capture (lambda () x)))
          (let ((x 2))
          (* x (capture)))))))

  (define scm_mystery_b
      (lambda ()
          (let ((x 1))
          (let ((capture (lambda () x)))
          (set! x 2)
          (* x (capture))))))

  (display (scm_mystery_a)) (newline)
  (display (scm_mystery_b)) (newline)

Re: I Want Off Mr. Golang's Wild Ride

#479
post #478

Earlier quoted context omitted.

Let's go on a journey. The answer is that I started with a hunch. You're treating x as a pointer sometimes, and a value other times. That seems strange, and unlike the python. In python the thing is always access the same way, it isn't a ptr type sometimes and a value type others. So first let's talk about scopes. In python, you aren't introducing a closure. If we do introduce a closure, like with an IIFE: def myster…

> The IIFE/outer closure here is equivalent to the capture happening in rust. So this is more equivalent to the rust examples than your python example. Wait, I don't follow. Rewriting your example to only use one lambda for clarity, we have: def mystery_closure_one_lambda(): x = 1 def capture(v): return lambda: v closure = capture(x) x = 2 return x * closure() So notice the lambda (i.e. what we are assigning to the v…

> So notice the lambda (i.e. what we are assigning to the variable 'closure') is now capturing v, not x, which is why it doesn't see the change we make to x, i.e., why it returns 2 instead of 4.

Yes, but this goes back to the scoping issue: in python, lambdas (and functions in general) don't capture. The only way to close over something is to pass as an argument. So to get the lexical closure behavior that rust provides, you have to add extra stuff in the python. This indeed makes the translations not mechanical (and you can add the lambda back in the rust, it doesn't hurt anything in these examples), but to get matching scoping behavior between rust and python, you need an extra layer of indirection in the python.

> Bringing in mutable reference data types like vec is I think not really relevent to what's at play here.

Of course it is, because in python everything is a reference. There's no such thing as a value type, and this is precisely where the difference in behavior comes in (other than the scoping issues). A rust RefCell is the thing that most naturally matches the actual in memory representation of a PyObject.

As for your digression, eww, although you forgot to actually do the sneaky part. This would be the actual demonstration, you need to modify the list in the closure (a real closure), and set it after the closure is created and before it is evaluated:

      # capture x in a closure
      def closure(v):
        def inner():
          mutate(v, 2)
          return v
        return inner

      capture = closure(ref)

      ref.set([3])

> Are you familiar with scheme?

Unfortunately not.

Re: I Want Off Mr. Golang's Wild Ride

#480
post #478

Earlier quoted context omitted.

> The IIFE/outer closure here is equivalent to the capture happening in rust. So this is more equivalent to the rust examples than your python example. Wait, I don't follow. Rewriting your example to only use one lambda for clarity, we have: def mystery_closure_one_lambda(): x = 1 def capture(v): return lambda: v closure = capture(x) x = 2 return x * closure() So notice the lambda (i.e. what we are assigning to the v…

> So notice the lambda (i.e. what we are assigning to the variable 'closure') is now capturing v, not x, which is why it doesn't see the change we make to x, i.e., why it returns 2 instead of 4. Yes, but this goes back to the scoping issue: in python, lambdas (and functions in general) don't capture. The only way to close over something is to pass as an argument. So to get the lexical closure behavior that rust provi…

Run my examples, they work! Python absolutely has real closures...
Post reply on HN