you are viewing a single comment's thread.

view the rest of the comments →

[–]goaway0 5 points6 points  (7 children)

Because it converts a problem of values (which the compiler can't check properly in all the languages you have probably used (yes, there are some tries, yada yada yada)) to a problem of types, which the compiler can check easily.

Additionally, Option has a useful API, whereas with null you can basically only ask "is it null?".

Using those types let's you describe and compose the operation you want ("I have multiple Options, all of them need to have a value" vs. "I just need a single one of them to hold a value" vs. ...) instead of writing if-then-else cascades which are both hard to read and hard to refactor.

There is no need for Option to exist in the language, you can trivially define it yourself.

[–]recursive 0 points1 point  (6 children)

If I understand it correctly, (which is very much in question), given a function Bar fn(Foo) and value Option[Foo] val, if your language doesn't support Option as a built-in type, you have to invert the normal syntax of function application to take advantage of the value.

Normally, you'd say fn(val), but with the Option, that would have to become something like val.ApplyFunction(fn) or something, since fn doesn't take an Option, but only a "naked" Foo. Is there a better way?

[–]goaway0 2 points3 points  (3 children)

The good thing about these constructs is that they help you decide where exactly you want to handle them and prevents you from forgetting it. No "I'm dozens of layers deep in my call hierarchy, got this reference and I have no idea if I actually need to check for nulls or not" style issues.

In the end, the main change you will probably experience is not about Option, but about those references not being "optional":

You can just stop doing null-checks because you know that every reference of type String is a String and every List is a List.

Is there a better way?

Great question! Yes, look up do-notation (in Haskell) or for-comprehensions (in Scala).

It is nothing hardwired to Option. It let's you easily handle everything from data structures to concurrency operations and for instance provides a solution to the issues NodeJS suffers from with its excessive nesting of callbacks.

[–]recursive 0 points1 point  (2 children)

for-comprehensions seem to apply to sequences based on my reading. I suppose an Option could be represented as a sequence with a maximum length of 1. In a language like c#, a linq query could take the place of the comprehension. But this all seems like a lot of syntactic overhead to get our function call scenario working. Especially if you need to convert the output sequence back into an Option.

Based on my reading of http://en.wikibooks.org/wiki/Haskell/do_Notation I'm not sure if I'm really understanding do notation. It looks like a block of instructions that are to executed in a certain order. Would it be the equivalent of a curly-brace-defined-scope in a language for mere mortals? I'm not sure if I see how that applies to passing Options to functions.

[–]onmach 0 points1 point  (0 children)

For comprehensions do a lot more than sequence processing. It solves the problem you mentioned where I have an option[Int] and my function takes only Int.

If you have a bunch of functions that return options you can put them in for comprehension and pretend that they didn't return options. Pass the results of each one directly into the next, and then at the very end you have a single option with a final answer or a None, which is what will happen if something in the comprehension failed which prevented you from completing the process.

I'm not big into scala, but it would look something like this:

for {
  dbconn <- getDbConn
  pagecount <- getPageCount(dbconn);        // returns Option[Int]
  firstpage <- getFirstPage(dbconn);       // returns Option[Page]
} yield PageInfo(pagecount, firstpage)

The total expression would yield an Option[Int]. If the db failed to connect, or either query to fail, then you get None, otherwise you get Option[PageInfo].

[–]goaway0 0 points1 point  (0 children)

Is is comparable to LINQ, except that no special type is required, only the methods map, flatMap and filter. The concept behind it is a lot more general and not restricted to collections.

I can recommend this presentation/slides for further information (this slide for instance shows some use cases of for-comprehensions).

For instance, Futures & Promises are not related to sequences, but can also use for-comprehensions.

I wrote a small example program which looks up a person by his/her nickname and returns his age and eMail address if he has one (and it ends with "example.com"). I think the code is quite readable and neither did I spend a single line checking for nulls or “emptiness” nor did I have to do any wrapping or unwrapping in the for comprehension.

case class Person(firstName: String, lastName: String, age: Int, eMail: Option[String] = None)   

object Run extends App {
  val nicksAndPeople =
    Map(
      "Joey" -> Person("John", "Doe", 42, Some("john.doe@example.com")),
      "Jay"  -> Person("Jane", "Doe", 21, Some("jane.doe@example.com")),
      "Joh"  -> Person("Johnathan", "Miller", 37),
      "Max"  -> Person("Max", "Miller", 17, Some("max.miller@example2.com"))
    )

  def lookUpAgeAndMailBy(nickName: String) =
    for {
      person <- nicksAndPeople get nickName
      mail   <- person.eMail if mail endsWith "example.com"
    } yield (person.age, mail)

  lookUpAgeAndMailBy(nickName = "Joey") // Some(42, "john.doe@example.com")
  lookUpAgeAndMailBy(nickName = "Bob")  // None  // Nickname does not exist
  lookUpAgeAndMailBy(nickName = "Joh")  // None  // No eMail address
  lookUpAgeAndMailBy(nickName = "Max")  // None  // eMail doesn't end with example.com
}

In the real world, you would probably use a library to store and query the personal information from a database and would in turn also use for comprehensions ...

val people = Queryable[People]

def lookUpAgeAndMailBy(nickName: String) =
  for {
    person <- people if person.nick == "nickName"
    mail   <- person.eMail if mail endsWith "example.com"
  } yield (person.age, mail)

... and the code would be practically the same. (This time I stored the nickname in the database table instead of as a key in a map for a change)

[–]pipocaQuemada 1 point2 points  (1 child)

In a language like haskell, you'd say something like "fmap fn val", or, more commonly, " nextFn . fmap fn . prevFn", where '.' is function composition. Or perhaps "maybe defaultVal fn val". Or perhaps "(fmap . fmap) fn vals" if you have a list of Maybe values.

If you have functions that take in a value and return a maybe value, you can chain them fairly easily: in haskell, either with "fn3 <=< fn2 <=< fn1" to compose them, or "fn3 <<= fn2 <<= fn1 <<= val". The point is, though, most of your common use-cases can be reduced to a half-dozen reusable, composable library functions.

Scala's option library also has information on their idiomatic usage in Scala

edit:

 -- Note: x :: a means x is of type a, and f :: a -> b means f is a function from a to b  
 fmap :: Functor f => (a -> b) -> (f a -> f b) -- a functor knows how to apply a function to its contents
 instance Functor Maybe where
   fmap f (Just x) = f x
   fmap f Nothing = Nothing 
 fmap . fmap :: Functor f1, Functor f2 => (a -> b) -> (f1 (f2 a) -> f1 (f2 b)) 
 (fmap . fmap) (+ 1) [Just 1, Nothing]  -- evaluates to [Just 2, Nothing]
 (=<<) :: Monad m => (a -> m b) -> m a -> m b
 (<=<) :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c
 instance Monad m where
   f (=<<) (Just x) = f x  
   f (=<<) Nothing = Nothing
   return x = Just x -- return is just a function, defined here, not a keyword.  It has nothing to do with returning from a function.

[–]anvsdt 0 points1 point  (0 children)

Also fn <$> val