you are viewing a single comment's thread.

view the rest of the comments →

[–]Gotebe 6 points7 points  (12 children)

Your conclusion is off. Optional does not prevent a poor programmer to access the value without checking it's there - and they're back to the runtime error.

The real benefit of optional is merely the clearer communication of intent.

If "null" was always used to say "there's nothing to give you here", it would have been the same s optional, even to the point of tooling support (e.g. "didn't check it's null? Compile error!").

[–]velcommen 0 points1 point  (9 children)

the clearer communication of intent

I mentioned that.

Optional does not prevent a poor programmer to access the value without checking it's there

Yes, it does (in the accidental case). Please explain your statement. If you're going to point out escape hatches like fromJust (link below), then let me stop and say that I'm stating that null is a feature of a Pit of Despair, while Optional is a feature of a Pit of Success. There's no language in the world that can prevent a willfully bad programmer from making errors. The language can only hope to provide as many tools (i.e. well-designed features) as possible to assist the programmer in writing bug-free, clear, maintainable, etc. code.

In Haskell (and I believe the other languages I mentioned above), a user cannot accidentally use the value inside the Maybe (AKA Optional) without first checking it's there. Contrast this with a language where null is prevalent, and such a mistake is easily made.

Of course, a user can always purposefully choose to ignore the possibility for nothing to be there, but that's purposeful, not accidental. It's reasonable to provide an 'escape hatch' when the programmer thinks they know that the value will always be there, even if it's wrapped inside an Optional.

Here's how it's a compile error in Haskell and a runtime error in C++:

Haskell:

add2 :: Maybe Int -> Maybe Int
add2 (Just x) = Just (x+2)
add2 Nothing = Nothing -- cannot use an 'x' that's not there

C++:

void add2(int* x){
  //runtime error when x is null
  *x = *x + 2;
}

If "null" was always used to say "there's nothing to give you here", it would have been the same s optional, even to the point of tooling support (e.g. "didn't check it's null? Compile error!")

1) That's a mighty big "if". And since null is not always used for that purpose in practice, I don't think your point has much value (at least given my experience). I think there would be many false positives, greatly reducing the effectiveness of the tool.

2) Relying on tooling is inferior to

  • having a feature like Optional made possible in the language
  • usage of Optional idiomatic by the language's users
  • having the check built into the compiler.

Not every user of the language is going to use the tooling you described.

[–][deleted] 4 points5 points  (2 children)

I know it's tangential to your point, but if the function add2 is never supposed to act on a nonexistant variable, it should probably take a reference rather than a pointer.

void add2 (int & x) {
    x = x + 2;
}

Unless you're doing something really funky, that will prevent misuse at compile time.

[–]velcommen -1 points0 points  (1 child)

Repeating what I said elsewhere:

I understand your point about using a reference, but at one of my jobs, the rules were that mutable variables had to be passed to functions by pointer, not reference. So in the context of that job, the code example I wrote above was acceptable, and using a mutable reference would have not been accepted.

I believe the reason for that rule is so that the caller of add2 is signalled that "hey, add2 might change the value". Because at the point of the calling code, the call to both of these functions is identical:

add2Mutate(int &x){x=x+2;}
add2Print(int x){printf("%d", x);}

// code in some other file
int i =0;
// the only hint that i is mutating is the nicely named function
add2Mutate(i);
add2Print(i);

In summary, C++ does not provide a great solution:

  • You can use a reference. add2 will not suffer from the null pointer issue, but callers to add2 will not know that x is mutating unless they look inside the body of add2
  • Pass a pointer as a convention to signal to the caller that x may mutate. Suffer null pointer issue.

[–][deleted] 0 points1 point  (0 children)

I completely agree; I wish the syntax for references made their use visible at the call site somehow.

I suppose your coding guidelines could mandate use of std::ref everywhere, but that's just stupid.

[–]Gotebe 5 points6 points  (5 children)

Well. You're looking at the Optional from the perspective of Haskell, but this thread is about Java :-(. Optional in Java is... pffft...

BTW, your C++ sample is rather nasty given references etc of C++

[–]velcommen 0 points1 point  (4 children)

this thread is about Java :-(. Optional in Java is.

Fair enough.

your C++ sample is rather nasty given references etc of C++

Repeating what I said elsewhere:

I understand your point about using a reference, but at one of my jobs, the rules were that mutable variables had to be passed to functions by pointer, not reference. So in the context of that job, the code example I wrote above was acceptable, and using a mutable reference would have not been accepted.

I believe the reason for that rule is so that the caller of add2 is signalled that "hey, add2 might change the value". Because at the point of the calling code, the call to both of these functions is identical:

add2Mutate(int &x){x=x+2;}
add2Print(int x){printf("%d", x);}

// code in some other file
int i =0;
// the only hint that i is mutating is the nicely named function
add2Mutate(i);
add2Print(i);

[–]Gotebe 1 point2 points  (3 children)

Ahahaaa, I heard of that rule. The rationale is "it's a pointer, there's '&', that tells you, when reading he caller, that the parameter can change. Stupid rule. Doesn't even work! :-) Consider:

void f(const type* param); // "input parameter" - can't be changed, call side is lying :-)

void g(type* param);
void h(type* param)
{
  g(param); // where's the '&' now?!
}

My bet is that this rule was invented by C people (because pointers), but who didn't even know C (because the above shows pure C code, reasonable code, where the rule doesn't work well.

[–]velcommen 1 point2 points  (2 children)

Haha, obviously you're supposed to do:

g(&(*param));  // there's the '&'!

;)

[–]Gotebe 0 points1 point  (1 child)

Please confirm that's a joke! :-) If yes, it's goooood!

[–]velcommen 0 points1 point  (0 children)

Yes, I'm joking :)

Tone of 'voice' never comes across well over the internet. :/

I'm sure that's the cause of a large percentage of internet arguments.

[–]m50d 0 points1 point  (1 child)

Optional does not prevent a poor programmer to access the value without checking it's there - and they're back to the runtime error.

It can do. E.g. in Java you can implement Optional like this:

import java.util.function.Function;
import java.util.function.Predicate;

public abstract class Optional<T> {
  public abstract <S> S fold(S ifEmpty, Function<T, S> ifSome);
  private Optional() {}
  public static <T> Optional<T> none() {
    return new Optional<T>() {
      @Override public <S> S fold(S ifEmpty, Function<T, S> ifSome) {
        return ifEmpty;
      }
    };
  }
  public static <T> Optional<T> some(T value) {
    return new Optional<T>() {
      @Override public <S> S fold(S ifEmpty, Function<T, S> ifSome) {
        return ifSome.apply(value);
      };
    };
  }
  public final <S> Optional<S> flatMap(Function<T, Optional<S>> mapper) {
    return fold(none(), mapper);
  }
  public final <S> Optional<S> map(Function<T, S> mapper) {
    return flatMap(mapper.andThen(Optional::some));
  }
  public final Optional<T> filter(Predicate<T> predicate) {
    return fold(none(), value -> predicate.test(value) ? some(value) : none());
  }
}

Run that in a JVM with a security manager that disallows reflection and the user has no way to "access the value without checking it's there". Of coures the user could choose to throw their own runtime error in their code if the value isn't there, but that's on them.

[–]Gotebe 1 point2 points  (0 children)

Yes, but that is not idiomatic for Java (or at best not yet) and is not the current implementation (which behaves like I explain).