5

In real terms, what is the difference between a "string" and a "string option"?

Aside from minor sytnax issues, the only difference I have seen is that you can pass a "null" to string while a string option expects a "none".

Brian
  • 117,631
  • 17
  • 236
  • 300
Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447

2 Answers2

28

I don't particularly like the answer I've typed up below, because I think the reader will either see it as 'preaching to the choir' or as 'some complex nonsense', but I've decided to post it anyway, in case it invites fruitful comment-discussion.

First off, it may be noteworthy to understand that

let x : string option = Some(null)

is a valid value, that indicates the presence (rather than absence) of a value (Some rather than None), but the value itself is null. (The meaning of such a value would depend on context.)

If you're looking for what I see as the 'right' mental model, it goes something like this...

The whole notion that "all reference types admit a 'null' value" is one of the biggest and most costly mistakes of .Net and the CLR. If the platform were resdesigned from scratch today, I think most folks agree that references would be non-nullable by default, and you would need an explicit mechanism to opt-in to null. As it stands today, there are hundreds, if not thousands of APIs that take e.g. "string foo" and do not want a null (e.g. would throw ArgumentNullException if you passed null). Clearly this is something better handled by a type system. Ideally, 'string' would mean 'non-null', and for the minority of APIs that do want null, you spell that out, e.g. "Nullable<string> foo" or "Option<string> foo" or whatever. So it's the existing .Net platform that's the 'oddball' here.

Many functional languages (such as ML, one of the main influences of F#) have known this forever, and so designed their type systems 'right', where if you want to admit a 'null' value, you use a generic type constructor to explicitly signal data that intentionally can have 'asbence of a value' as a legal value. In ML, this is done with the "'t option" type - 'option' is a fine, general-purpose solution to this issue. F#'s core is compatible (cross-compiles) with OCaml, an ML dialect, and thus F# inherits this type from its ML ancestry.

But F# also needs to integrate with the CLR, and in the CLR, all references can be null. F# attempts to walk a somewhat fine line, in that you can define new class types in F#, and for those types, F# will behave ML-like (and not easily admit null as a value):

type MyClass() = class end
let mc : MyClass = null  // does not compile

however the same type defined in a C# assembly will admit null as a proper value in F#. (And F# still allows a back-door:

let mc : MyClass = Unchecked.defaultof<_> // mc is null

to effectively get around the normal F# type system and access the CLR directly.)

This is all starting to sound complicated, but basically the F# system lets you pretty much program lots of F# in the 'ML' style, where you never need to worry about null/NullReferenceExceptions, because the type system prevents you from doing the wrong things here. But F# has to integrate nicely with .Net, so all types that originate from non-F# code (like 'string') still admit null values, and so when programming with those types you still have to program as defensively as you normally do on the CLR. With regards to null, effectively F# provides a haven where it is easy to do 'programming without null, the way God intended', but at the same time interoperate with the rest of .Net.

I haven't really answered your question, but if you follow my logic, then you would not ask the question (or would unask it, a la Joshu's MU from "Godel, Escher, Bach").

Brian
  • 117,631
  • 17
  • 236
  • 300
  • 1
    It didn't have to be this way. They could have mapped CLR functions to "option" instead of "String" unless they were marked as never returning null. And in .NET 4, the functions are supposed to be marked as such. – Jonathan Allen Jun 04 '09 at 00:16
  • I like this answer, a lot of added value to StackOverflow. – Benjol Jun 04 '09 at 12:13
  • 1
    If only I could 'favorite' answers. Ah well. Anywho, if they did that, Grauren, we'd have a nightmare of a time pattern-matching every 'option' that the framework spits out. Thankfully, there aren't that many functions that can return nulls, so you don't have to be too cautious. – YotaXP Jun 04 '09 at 20:20
  • +1! "int" means "int value". "string" means "string value or no value". "Guid" means "Guid value". (In C# 4) "Tuple" means "Tuple value or no value". Makes perfect sense. – MichaelGG Aug 07 '09 at 04:18
  • @Jonathan - If F# were to automatically make all CLR functions into option types, it would not only needlessly complicate things, but also misrepresent the intention of the option type, lessening the value of the whole idea. I'm glad there's a distinction. – Robert Jeppesen Mar 09 '11 at 13:32
4

I think you could reclassify this as a more general question

What is the difference between option random ref type and just a random ref type

The difference is with an option, you are providing an explicit empty value case. It's a declarative way of saying "I might not provide a value". A option of value None unambiguously represents a lack of a value.

Often times people use null to represent a lack of a value. Unfortunately this is ambiguous to the casual reader because it's unknown if null represents a valid value or the lack of a value. Option removes this ambiguity.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • How does it remove the ambiguity? From where I'm sitting there is no difference between binding a null to a variable and binding a none to a variable. – Jonathan Allen Jun 03 '09 at 23:20
  • @Grauenwolf the problem is null is often a valid non-empty value. Option though is meant as an explicit "i have no value". I think the best way to understand the difference is to consider that it's possible to have an option with a Some value of null. – JaredPar Jun 03 '09 at 23:41
  • So now we get to check for both None-style nulls and null-style nulls? This isn't fun. – Jonathan Allen Jun 03 '09 at 23:56
  • @Grauenwolf this was a big sticking point for me in the begining but I've now embraced this and used this style of coding in all of my code. The reason why is that option is explicit documentation to people using your API that a returned value may indeed not have a value. It eliminates the questions around APIs like Foo FindAFoo();. If no Foo is found does it throw or return null? It's not possible to tell from the API. However Option FindAFoo() is quite clearer. – JaredPar Jun 04 '09 at 00:02
  • My problem isn't with F# types + option. My problem is the way it handles CLR types + option. Instead of attempting to address the problem, they confused the issue by making the F# developer deal with two types of nulls. – Jonathan Allen Jun 04 '09 at 00:13
  • 1
    For the most part, this isn't really an issue when using F# because most F# types (e.g. sum types, tuples, record types, etc.) don't have null as a valid value, meaning that you only have to check for None and not also Some(null). Even if you do have to check for both, it's pretty easy to put both in a match statement: match str with | None | Some(null) -> failwith "No valid string value" | Some s -> ... – kvb Jun 04 '09 at 03:09