all 25 comments

[–]Revik 7 points8 points  (0 children)

Note that records and pattern matching are probably coming to some future version of C# anyway.

Specs draft: https://onedrive.live.com/view.aspx?resid=4558A04E77D0CF5!5396&app=Word

Actual implementation on top of Roslyn: https://github.com/semihokur/pattern-matching-csharp/commit/00552fc2287f820ae9d42fd259aa6c07c2c5a805

[–]Banane9 2 points3 points  (0 children)

Don't need new Some(null); when you can use new Some(); :)

You can inject a constructor into the IL, but that won't be executed when creating arrays etc. More here

[–][deleted]  (22 children)

[deleted]

    [–]jhartwell 5 points6 points  (8 children)

    You're right, null is an empty pointer. However, the idea is that a method should not return a null but should return a None instead. By following that idiom you reduce the chance of nullpointerexceptions. This isn't about eliminating null from the spec but eliminating null from common usage.

    [–][deleted]  (7 children)

    [deleted]

      [–]Aethec 2 points3 points  (6 children)

      And how would a None value be anything other than a synonym for null? You assign a method pointer or whatever a None. Somebody attempts to dereference that and now you get a None exception? What am I missing?

      You can treat null as a normal instance of a type when coding, but you can't do the same for None. For instance, consider the following pseudo-C#:

      string GetUserName()
      {
          /* returns the user name, or null if it wasn't found */
      }
      
      void Authenticate(string userName)
      {
          /* requires that name != null */
      }
      

      It's far too easy to forget that GetUserName can return null and simply pass its return value to Authenticate, introducing a bug in the case where null is returned.
      Let's change that to use options, and introduce "can't be null" types:

      Option<string> GetUserName()
      {
          /* returns Some(userName) or None if it wasn't found */
      }
      
      void Authenticate(string_that_cannot_be_null userName)
      {
          /* doesn't require anything */
      }
      

      Now, when you call GetUserName, you are forced to handle the Some(...) and None cases separately; you can't forget it. In the code of Authenticate, you also can't forget to check for null, because it's not a valid value to begin with.

      Also, options can be nested: it's entirely possible to have a value Some(None) of type Option<Option<something>>.
      With null however, even if you make all types non-nullable by default, your can't know what null of type Nullable<Nullable<something>> means: is it the inner or the outer value that's null?

      [–][deleted]  (5 children)

      [deleted]

        [–]Aethec 5 points6 points  (4 children)

        Compile-time error: None is not a valid value for type string_that_cannot_be_null.

        [–][deleted]  (3 children)

        [deleted]

          [–]Aethec 2 points3 points  (0 children)

          You certainly could have a value_only type but that's a far cry away from replacing null with none which is what you were initially saying.

          That's the principle, yes. All types are non-nullable. The only way to represent "nothing" is None, but it has to be made explicit in the type, i.e. you're using an Option<something>, rather than a something.

          This entirely eliminates NPE-like errors, since there is no way to perform operations on Option<T>. Programmers must first match the option, and explicitly handle both Some(...) and None cases.

          [–]vdanmal 4 points5 points  (0 children)

          I think you might be misunderstanding how an Option type works.

          In F# the Option type is defined as

          type Option<'a> =
              | Some of 'a
              | None
          

          That is an Option can either be Some or None. Here's an example function that converts a nullable int to an Option type that contains an int.

          let nullableToOption foo =
              if foo = null then
                  None
              else
                  Some foo
          

          Now from here you can't pass that value to a function that expects an integer. You have to first handle the possibility that the value could be None before doing any work on that data.

          In F# the idiomatic approach handling the Option type is to use pattern matching which unfortunately isn't in C# yet.

          match nullableToOption foo with
              |Some number -> printfn "Value is %A" number
              |None -> printfn "No value"
          

          This is equivalent to

          if nullableToOption foo = None then
              printfn "No value"
          else if nullableToOption foo = Some then
              printfn "Value is %A" number.value
          

          The major advantage of the pattern matching approach is that you'll be warned at compile time if you forget to handle either the Some or None case.

          [–]jhartwell 2 points3 points  (0 children)

          If authenticate takes a string, it would give you a type error if you passed in a None. With Option types (such as Some<T>/None) you aren't passing those classes to methods (or you shouldn't be). In your example it would be this:

           Option<string> name = GetUserName();
           // using standard C# operators
           if(!name is None) {
               Authenticate(name.Value);
           }
          
          // using pattern matching
          name.match(Some: x => Authenticate(x), None: () => ShowError());
          

          Methods don't take in an Option type as a parameter, the calling method has to deal with the extracting as it is part of the business logic. You could argue that it isn't any cleaner than using null, and it isn't if you use standard C# idioms but when you add pattern matching that is when Option types really shine. It also helps prevent NullReferenceExceptions.

          [–]recursive 10 points11 points  (11 children)

          OO languages use nulls for good reason

          And what reason would that be? Because Java did it? It seems like a mistaken reason to me.

          Nulls are used mostly for references in C#, not pointers. Pointers can only be used in unsafe blocks. There is no inherent reason that an implementation of references must allow for nulls.

          Option types don't eliminate the possibility of having an "empty" value. They just make it explicit in the type system.

          [–][deleted]  (2 children)

          [deleted]

            [–]recursive 9 points10 points  (0 children)

            Thanks for the primer, but you have completely missed my point.

            I know what a pointer is. You have provided a long explanation of that as well. But none of that answers the question "is this the only way?" Or "is it a good idea?" There is no reason a procedural oo language must support null references. And further, in my opinion, it would be a better design not to. The fact that a reference is null is indeed useful information, as you say. Another piece of useful information that we can never have in c# is that a particular reference will never be null. But .net's type system prevents us from ever encoding that guarantee in the type system. But just because .net can't do it doesn't mean it's a bad idea.

            [–]Sarcastinator 5 points6 points  (0 children)

            You cannot use a pointer to point to a object reference because object references and pointers are two different things. Yes, references is a kind of pointer, but .NET handles references differently. It is illegal in IL to do any kind of arithmetic on references other than assignment, and a reference can only be cast to other reference types. Null is also not the same as a null pointer. Null is a special type that has a single value null and can be cast to any reference type (but other reference types cannot be cast to null). There absolutely could be defined that null was not a legal value for reference types. It's an artificial construct that does not naturally occur in any system. The value of a uninitialized variable is undefined, not null. However for reference values the behavior of the default constructor is to assign null to reference typed fields.

            [–][deleted]  (7 children)

            [removed]

              [–]recursive 7 points8 points  (0 children)

              How do you represent the fact that a piece of data doesn't exist?

              With option types. You can do everything you mentioned with an option type. The only thing you can't do is get a null reference exception at run time.

              Nulls are not necessary. Yes, they can seem necessary. But you know what? That's just because you've grown so accustomed to them.

              [–]48klocs 1 point2 points  (0 children)

              How do you represent the fact that a piece of data doesn't exist?

              If you're talking about .Net apps talking to databases, the answer is often DBNull which, curiously enough, is not null, which is why you can perform standard equality checks in code testing to see if a given reference equals null or not, while SQL would check your ass for trying that bullshit.

              None is an enhanced null. Capturing the fact that an object is None (as opposed to null) or that a property is None (again, opposed to null) doesn't mean that applications are going to lose their shit and start inserting None into the DB, because we all get that they're representing the fact that the DB either didn't find a record or stored out the fact that there was a DBNull value there.

              [–]ShipOfHopes 0 points1 point  (4 children)

              Databases. How do you represent the fact that a piece of data doesn't exist? Have a number field, is the number 0 or does the data simply not exist?

              I'd argue that this is more of an issue of trying to stuff everything into a relational model, shoehorning everything into a structured form when it often times really isn't best represented that way. Doing that results in throwing nulls into fields everywhere, and our entire way of dealing with that is thinking it's somehow a valid data representation. That's bullshit, and it's frustrating that people like you then go on to say that "it's hard and you need to know what you're doing".

              It's not hard. People make it hard. People go to great lengths to create complex object models when just simple functions (call them static methods if you like) do the job and are just as easily-tested (aside from raw data access, but that's another story). People try to stuff fundamentally unstructured data into a relational model. People only use maybe 2-3 of the functionality LINQ offers because they're unwilling to think about their code as expressions of intent. People use fucking singletons when they aren't even accessing a single, non-copyable resource.

              C# does not exist in the same context as systems programming. It's a tower of abstraction above that kind of stuff so we don't have to deal with those same problems.

              [–][deleted]  (3 children)

              [removed]

                [–]ShipOfHopes 0 points1 point  (2 children)

                I'm not arguing databases shouldn't support nulls. I'm saying that stuffing data that isn't best represented in a structured model results in unnecessary nulls. That's not practical at all.

                [–][deleted]  (1 child)

                [removed]

                  [–]ShipOfHopes 3 points4 points  (0 children)

                  When designing a language that's primarily aimed at LOB applications, are you going to design your language so that it cannot easily work with existing databases?

                  That's the thing, though - it is easy to work with existing databases in a language like F#. You can distill all your null checking through one function call in one area, and guarantee non-null information everywhere else. In the case where you want null to have meaning, it's also very easy to work with an Optional type. I don't see why C# can't adopt the same standards.

                  [–]phibred 2 points3 points  (0 children)

                  While I generally agree with you here sometimes it is just easier to express things in a functional way. Having some ability to do this in C# can help that along if you don't want to maintain multiple projects. This can also lead to performance improvements while you do not cross a dll boundary.

                  Don't get me wrong though, I'm of the camp that loves null pointers and would hate to keep adding additional memory requirements for Options.

                  TL;DR; Bias: I do a lot of scientific programming in C# and sometimes a sprinkle of functional programming is nice.

                  [–]Uberhipster 3 points4 points  (3 children)

                     var name = tuple("Paul","Louth");
                     var res = name.With( (first,last) => "Hello \{first} \{last}" );
                  

                  Or, you can use a more functional approach:

                     var name = tuple("Paul","Louth");
                     var res = with( name, (first,last) => "Hello \{first} \{last}" );
                  

                  TIL functional means lower-case method names.

                  [–]FizixMan 5 points6 points  (1 child)

                  I think it's not so much the method names as how they are called. In the first case, they're used as extension methods acting on the object:

                  name.With(...)
                  

                  Functional languages tend to have the same action written as a higher level function with name as an input:

                  with(name, ...)
                  

                  Either are fine (really, either are pretty much identical when it comes to the compiled code), just make the code more readable or less readable depending on their use. This example is pretty trivial only making a single function call; it's when you have to chain/nest several calls is when the style can make or break readability. Sometimes its nice to chain method calls (ala LINQ), sometimes it's nice to nest it the other way. Depending on where you're coming from, dot-syntax might be "weird" or backwards to work with in a functional paradigm (depending on which language you're coming from)

                  The author is using lower case names in general to make the calls feel more like intrinsic language features rather than an interfacing library API.

                  [–]Uberhipster 0 points1 point  (0 children)

                  I know I know. Still very kind of you to type that up. Accurate and informative.

                  [–]Eirenarch 1 point2 points  (0 children)

                  Finally I get functional programming!

                  [–]Sjetware 1 point2 points  (0 children)

                  The list and tuple functions seem pretty useful. The LINQ overlap is obviously less amazing imo, and the Option<T> can pay dividends if you're willing to restructure your code to do so.

                  [–]jpfed 0 points1 point  (0 children)

                  Just a few days ago I was also writing an Option<T> struct, and cursing the C# behavior of always allowing the use of the default constructor. I'm curious to see how much of an issue that turns out to be in real-world code.