<commit_before>package fpinscala.errorhandling

// hide std library `Option`, `Some` and `Either`, since we are writing our own in this chapter
import scala.{Option => _, Some => _, Either => _, _} 

sealed trait Option[+A] {
  
  // map: Type B => Option[A] => (A => B) => Option[B]
  // map[B] takes an Option[A] and a A => B and returns
  //        -- None if not Some(a), 
  //        -- Some(f(a)) otherwise
  def map[B](f: A => B): Option[B] = this match {
    case None => None
    case Some(a) => Some(f(a)) 
  }
  // N.B. the Option[A] argument type appears because map is applied to `this`
  //  
  // We use the above map function when f is total... 
  // ...so, why does map return Option[B]? Because,
  // the thing to which it is applied might come back empty.
  // Example: lookupByNmae("Joe").map(_.department)
  // Here, lookupByName("Joe") could fail.  If it succeeds, then
  // the _.department function works as expected (it's total).

  // getOrElse : Type B => Option[A] => B 
  // getOrElse[B] takes an Option[A] and returns
  //       -- a if Some(a),
  //       -- d otherwise.
  def getOrElse[B>:A](default: => B): B = this match {
    case None => default
    case Some(a) => a
  }
  // IMPORTANT: the type annotation `default: => B` means that the
  // argument named `default` is passed using call-by-name evaluation. 
  // This means it will not be evaluated unless/until needed.

  // flatMap: Type B => Option[A] => (A => Option[B]) => Option[B]
  // flatMap[B] takes an Option[A] and a A => Option[B] and returns
  //        -- None if not Some(a), 
  //        -- f(a) otherwise
  def flatMap[B](f: A => Option[B]): Option[B] =  map(f) getOrElse(None)
  // The function `f: A => Option[B]` passed to flatMap can fail.
  // This is a "flattening" map because it takes f:A=>Option[B];
  // If we had tried to define flatMap as simply map(f), we get a type error
  // because then the return type would be Option[Option[B]].
  
  // we can also implement `flatMap` with explicit pattern matching 
  // (which seems much clearer to me).
  def flatMap_pm[B](f: A => Option[B]): Option[B] = this match {
    case None => None
    case Some(a) => f(a)
  }
  
  // orElse: Type B => Option[A] => Option[B] => Option[B]
  // orElse[B] takes this a: Option[A] and ob:Option[B] and returns
  //      -- Some(a) if Some(a)
  //      -- ob otherwise
  def orElse[B>:A](ob: => Option[B]): Option[B] = map(Some(_)) getOrElse(ob) 

  def orElse_pm[B>:A](ob: => Option[B]): Option[B] = this match {
    case None => ob
    case Some(a) => Some(a)
  }
  
  // filter := return Some(a) if f(a) true, else return None
  def filter_pm(f: A => Boolean): Option[A] = this match {<commit_msg>minor mods<commit_after>package fpinscala.errorhandling

// hide std library `Option`, `Some` and `Either`, since we are writing our own in this chapter
import scala.{Option => _, Some => _, Either => _, _} 

sealed trait Option[+A] {
  
  // map: Type B => Option[A] => (A => B) => Option[B]
  // map[B] takes an Option[A] and a A => B and returns
  //        -- None if not Some(a), 
  //        -- Some(f(a)) otherwise
  def map[B](f: A => B): Option[B] = this match {
    case Some(a) => Some(f(a)) 
    case _ => None
  }
  // N.B. the Option[A] argument type appears because map is applied to `this`
  //  
  // We use the above map function when f is total... 
  // ...so, why does map return Option[B]? Because,
  // the thing to which it is applied might come back empty.
  // Example: lookupByNmae("Joe").map(_.department)
  // Here, lookupByName("Joe") could fail.  If it succeeds, then
  // the _.department function works as expected (it's total).

  // getOrElse : Type B => Option[A] => B 
  // getOrElse[B] takes an Option[A] and returns
  //       -- a if Some(a),
  //       -- d otherwise.
  def getOrElse[B>:A](default: => B): B = this match {
    case Some(a) => a
    case _ => default
  }
  // IMPORTANT: the type annotation `default: => B` means that the
  // argument named `default` is passed using call-by-name evaluation. 
  // This means it will not be evaluated unless/until needed.

  // flatMap: Type B => Option[A] => (A => Option[B]) => Option[B]
  // flatMap[B] takes an Option[A] and a A => Option[B] and returns
  //        -- None if not Some(a), 
  //        -- f(a) otherwise
  // we first try the simpler pm implementation:
  def flatMap_pm[B](f: A => Option[B]): Option[B] = this match {
    case Some(a) => f(a)
    case _ => None
  }

  // now for the implementation that uses map and getOrElse:
  def flatMap[B](f: A => Option[B]): Option[B] =  map(f) getOrElse(None)
  // Notice that since `f: A => Option[B]`, if x:Option[A], then
  // x map(f): Option[Option[B]].  But we want to return an Option[B],
  // so we must unravel it one level with getOrElse.
  // This is a "flattening" because it composes two things, both of which
  // can fail---the "this" and the function evaluation f(Some(a))---and
  // instead of returning Option[Option[B]], it just returns Option[B],
  // (hence, flattening).
  
  // orElse: Type B => Option[A] => Option[B] => Option[B]
  // orElse[B] takes this a: Option[A] and ob:Option[B] and returns
  //      -- Some(a) if Some(a)
  //      -- ob otherwise
  def orElse[B>:A](ob: => Option[B]): Option[B] = map(Some(_)) getOrElse(ob) 

  def orElse_pm[B>:A](ob: => Option[B]): Option[B] = this match {
    case Some(a) => Some(a)
    case _ => ob
  }
  
  // filter := return Some(a) if f(a) true, else return None
  def filter_pm(f: A => Boolean): Option[A] = this match {
{'fraction_non_alphanumeric': 0.16318443804034583, 'fraction_numerical': 0.0, 'mean_word_length': 2.658102766798419, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 80, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5627474', 'n_tokens_mistral': 1949, 'n_tokens_neox': 1874, 'n_words': 668}