// -*- coding: utf-8 -*-
// ---
// jupyter:
//   jupytext:
//     text_representation:
//       extension: .scala
//       format_name: light
//       format_version: '1.5'
//       jupytext_version: 1.14.4
//   kernelspec:
//     display_name: Scala
//     language: scala
//     name: scala
// ---

// # Programming Scala: Examples 2
//
// @author weaves
//
// Introductory examples

// ## Basics

// ### Object-Orientation

// +
// * @author weaves

// * Introductory examples

// +
// ** src/main/scala/progscala2/basicoop/PersonAuxConstructors3.sc
import progscala2.basicoop.Address
import progscala2.basicoop3.Person3

val a1 = new Address("1 Scala Lane", "Anytown", "CA", "98765")
val a2 = new Address("98765")

Person3("<NAME>")                                    // Primary
// Result: Person3(Buck Trends1,None,None)

Person3("<NAME>", Some(20), Some(a1))                // Primary
// Result: Person3(Buck Trends2,Some(20),
//           Some(Address(1 Scala Lane,Anytown,CA,98765)))

Person3("<NAME>", 20, a1)
// Result: Person3(Buck Trends3,Some(20),
//           Some(Address(1 Scala Lane,Anytown,CA,98765)))

Person3("<NAME>", Some(20))                          // Primary
// Result: Person3(Buck Trends4,Some(20),None)

Person3("<NAME>", 20)
// Result: Person3(Buck Trends5,Some(20),None)

Person3("<NAME>", address = Some(a2))                // Primary
// Result: Person3(Buck Trends6,None,
//           Some(Address([unknown],Anytown,CA,98765)))

Person3("<NAME>", address = a2)
// Result: Person3(Buck Trends7,None,
//           Some(Address([unknown],Anytown,CA,98765)))

// +
// ** src/main/scala/progscala2/basicoop/ValueClassDollar.sc

class Dollar(val value: Float) extends AnyVal {
  override def toString = "$%.2f".format(value)
}

val benjamin = new Dollar(100)

// +
// ** src/main/scala/progscala2/basicoop/EmployeeSubclass.sc
import progscala2.basicoop.Address

case class Person(    // This was Person2 previously, now renamed.
  name: String,
  age: Option[Int] = None,
  address: Option[Address] = None)

class Employee(                                                      // <1>
  name: String,
  age: Option[Int] = None,
  address: Option[Address] = None,
  val title: String = "[unknown]",                                   // <2>
  val manager: Option[Employee] = None) extends Person(name, age, address) {

  override def toString =                                            // <3>
    s"Employee($name, $age, $address, $title, $manager)"
}

val a1 = new Address("1 Scala Lane", "Anytown", "CA", "98765")
val a2 = new Address("98765")

val ceo = new Employee("<NAME>", title = "CEO")
// Result: Employee(Joe CEO, None, None, CEO, None)

new Employee("<NAME>")
// Result: Employee(Buck Trends1, None, None, [unknown], None)

new Employee("<NAME>", Some(20), Some(a1))
// Result:  Employee(Buck Trends2, Some(20),
//            Some(Address(1 Scala Lane,Anytown,CA,98765)), [unknown], None)

new Employee("<NAME>", Some(20), Some(a1), "Zombie Dev")
// Result:  Employee(<NAME>, Some(20),
//            Some(Address(1 Scala Lane,Anytown,CA,98765)), Zombie Dev, None)

new Employee("<NAME>", Some(20), Some(a1), "Zombie Dev", Some(ceo))
// Result:  Employee(<NAME>, Some(20),
//            Some(Address(1 Scala Lane,Anytown,CA,98765)), Zombie Dev,
//            Some(Employee(Joe CEO, None, None, CEO, None)))

// +
// ** src/main/scala/progscala2/basicoop/ValueClassUniversalTraits.sc

trait Digitizer extends Any {
  def digits(s: String): String = s.replaceAll("""\D""", "")         // <1>
}

trait Formatter extends Any {                                        // <2>
  def format(areaCode: String, exchange: String, subnumber: String): String =
    s"($areaCode) $exchange-$subnumber"
}

class USPhoneNumber(val s: String) extends AnyVal 
    with Digitizer with Formatter {

  override def toString = {
    val digs = digits(s)
    val areaCode = digs.substring(0,3)
    val exchange = digs.substring(3,6)
    val subnumber  = digs.substring(6,10)
    format(areaCode, exchange, subnumber)                            // <3>
  }
}

val number = new USPhoneNumber("987-654-3210")
// Result: number: USPhoneNumber = (987) 654-3210

// +
// ** src/main/scala/progscala2/basicoop/PersonAuxConstructors2.sc
import progscala2.basicoop.Address

val a1 = new Address("1 Scala Lane", "Anytown", "CA", "98765")
val a2 = new Address("98765")

case class Person2(
  name: String,
  age: Option[Int] = None,
  address: Option[Address] = None)

new Person2("<NAME>")
// Result: Person2 = Person2(Buck Trends1,None,None)

new Person2("<NAME>", Some(20), Some(a1))
// Result: Person2(Buck Trends2,Some(20),
//           Some(Address(1 Scala Lane,Anytown,CA,98765)))

new Person2("<NAME>", Some(20))
// Result: Person2(Buck Trends3,Some(20),None)

new Person2("<NAME>", address = Some(a2))
// Result: Person2(Buck Trends4,None,

// +
// ** src/main/scala/progscala2/basicoop/PersonEmployeeTraits.sc
import progscala2.basicoop.{ Address, Person, Employee }

val ceoAddress  = Address("1 Scala Lane", "Anytown", "CA", "98765")
// Result: ceoAddress: oop2.Address = Address(1 Scala Lane,Anytown,CA,98765)

val buckAddress = Address("98765")
// Result: buckAddress: oop2.Address = Address([unknown],Anytown,CA,98765)

val ceo = Employee(
  name = "<NAME>", title = "CEO", age = Some(50),
  address = Some(ceoAddress), manager = None)
// Result: ceo: oop2.Employee = Employee(Joe CEO,Some(50),
//            Some(Address(1 Scala Lane,Anytown,CA,98765)),CEO,None)

val ceoSpouse = Person("<NAME>", address = Some(ceoAddress))
// Result: ceoSpouse: oop2.Person = Person(<NAME>,None,
//            Some(Address(1 Scala Lane,Anytown,CA,98765)))

val buck = Employee(
  name = "<NAME>", title = "Zombie Dev", age = Some(20),
  address = Some(buckAddress), manager = Some(ceo))
// Result: buck: oop2.Employee = Employee(<NAME>,Some(20),
//             Some(Address([unknown],Anytown,CA,98765)),Zombie Dev,
//             Some(Employee(Joe CEO,Some(50),
//               Some(Address(1 Scala Lane,Anytown,CA,98765)),CEO,None)))

val buckSpouse = Person("<NAME>", address = Some(buckAddress))
// Result: buckSpouse: oop2.Person = Person(<NAME>,None,
//             Some(Address([unknown],Anytown,CA,98765)))

// +
// ** src/main/scala/progscala2/basicoop/PersonAuxConstructors.sc
import progscala2.basicoop.{Address, Person}

val a1 = new Address("1 Scala Lane", "Anytown", "CA", "98765")
// Result: Address(1 Scala Lane,Anytown,CA,98765)

val a2 = new Address("98765")
// Result: Address([unknown],Anytown,CA,98765)

new Person("<NAME>")
// Result: Person(<NAME>1,None,None)

new Person("<NAME>", Some(20), Some(a1))
// Result: Person(<NAME>,Some(20),
//           Some(Address(1 Scala Lane,Anytown,CA,98765)))


new Person("<NAME>", 20, a2)
// Result: Person(<NAME>,Some(20),
//           Some(Address([unknown],Anytown,CA,98765)))

new Person("<NAME>", 20)
// Result: Person(<NAME>4,Some(20),None)

// +
// ** src/main/scala/progscala2/basicoop/Zipcode.sc
import progscala2.basicoop.ZipCode

ZipCode(12345)
// Result: ZipCode = 12345

ZipCode(12345, Some(6789))
// Result: ZipCode = 12345-6789

ZipCode(12345, 6789)
// Result: ZipCode = 12345-6789

try {
  ZipCode(0, 6789)  // Invalid Zip+4 specified: 0-6789
} catch {
  case e: java.lang.IllegalArgumentException => e
}

try {
  ZipCode(12345, 0)  // Invalid Zip+4 specified: 12345-0
} catch {
  case e: java.lang.IllegalArgumentException => e
}

// +
// ** src/main/scala/progscala2/basicoop/ValueClassPhoneNumber.sc

class USPhoneNumber(val s: String) extends AnyVal {

  override def toString = {
    val digs = digits(s)
    val areaCode  = digs.substring(0,3)
    val exchange  = digs.substring(3,6)
    val subnumber = digs.substring(6,10)  // "subscriber number"
    s"($areaCode) $exchange-$subnumber"
  }

  private def digits(str: String): String = str.replaceAll("""\D""", "") 
}

val number = new USPhoneNumber("987-654-3210")
// Result: number: USPhoneNumber = (987) 654-3210

// +
// ** src/main/scala/progscala2/basicoop/Complex.sc

case class Complex(real: Double, imag: Double) {
  def unary_- : Complex = Complex(-real, imag)                       // <1>
  def -(other: Complex) = Complex(real - other.real, imag - other.imag)
}

val c1 = Complex(1.1, 2.2)
val c2 = -c1                           // Complex(-1.1, 2.2)
val c3 = c1.unary_-                    // Complex(-1.1, 2.2)
val c4 = c1 - Complex(0.5, 1.0)        // Complex(0.6, 1.2)
// -

// #### Equality

// +
// ** src/main/scala/progscala2/objectsystem/equality/person-equality.sc

case class Person(firstName: String, lastName: String, age: Int)

val p1a = Person("Dean", "Wampler", 29)
val p1b = Person("Dean", "Wampler", 29)
val p2  = Person("Buck", "Trends",  30)

p1a equals p1a     // = true
p1a equals p1b     // = true
p1a equals p2      // = false
p1a equals null    // = false
try {
  null equals p1a  // throws java.lang.NullPointerException
} catch {
  case e: java.lang.NullPointerException => e
}
try {
  null equals null // throws java.lang.NullPointerException
} catch {
  case e: java.lang.NullPointerException => e
}

p1a == p1a         // = true
p1a == p1b         // = true
p1a == p2          // = false
p1a == null        // = false
null == p1a        // = false
null == null       // = true  (compiler warns that it's always true.)

p1a != p1a         // = false
p1a != p1b         // = false
p1a != p2          // = true
p1a != null        // = true
null != p1a        // = true
null != null       // = false (compiler warns that it's always false.)

p1a eq p1a         // = true
p1a eq p1b         // = false
p1a eq p2          // = false
p1a eq null        // = false
null eq p1a        // = false
null eq null       // = true  (compiler warns that it's always true.)

p1a ne p1a         // = false
p1a ne p1b         // = true
p1a ne p2          // = true
p1a ne null        // = true
null ne p1a        // = true
null ne null       // = false (compiler warns that it's always false.)

Array(1, 2) == Array(1, 2)              // = false
Array(1, 2) sameElements Array(1, 2)    // = true

List(1, 2) == List(1, 2)                // = true
List(1, 2) sameElements List(1, 2)      // = true

// -

// ### Functional Programming

// +
// ** src/main/scala/progscala2/fp/curry/curried-func2.sc

val f1: String =>  String => String  = (s1: String) => (s2: String) => s1 + s2
val f2: String => (String => String) = (s1: String) => (s2: String) => s1 + s2

f1("hello")("world")
// helloworld

f2("hello")("world")
// helloworld

val ff1 = Function.uncurried(f1)
val ff2 = Function.uncurried(f2)

ff1("hello", "world")
// helloworld

ff2("hello", "world")

// +
// ** src/main/scala/progscala2/fp/curry/lifted-func.sc

val finicky: PartialFunction[String,String] = {
  case "finicky" => "FINICKY"
}

val finickyOption = finicky.lift

finicky("finicky")
try {
  finicky("other")
} catch {
  case e: scala.MatchError => e
}

finickyOption("finicky")
finickyOption("other")

val finicky2 = Function.unlift(finickyOption)

finicky2("finicky")
try {
  finicky2("other")
} catch {
  case e: scala.MatchError => e
}

// +
// ** src/main/scala/progscala2/fp/curry/tupled-func.sc

def mult(d1: Double, d2: Double, d3: Double) = d1 * d2 * d3

val d3 = (2.2, 3.3, 4.4)

mult(d3._1, d3._2, d3._3)

val multTupled = Function.tupled(mult _)
// multTupled: ((Double, Double, Double)) => Double = <function1>

multTupled(d3)

val multUntupled = Function.untupled(multTupled)

multUntupled(d3._1, d3._2, d3._3)

// +
// ** src/main/scala/progscala2/fp/curry/curried-func.sc

def cat1(s1: String)(s2: String) = s1 + s2
def cat2(s1: String) = (s2: String) => s1 + s2
def cat3(s1: String, s2: String) = s1 + s2

val cat3Curried = (cat3 _).curried
// cat3Curried: String => (String => String) = <function1>

cat3Curried("hello")("world")
// helloworld

cat3("hello", "world")
// helloworld

val cat3Uncurried = Function.uncurried(cat3Curried)
// cat3Uncurried: (String, String) => String = <function2>

cat3Uncurried("hello", "world")

// +
// * @author weaves

// * Introductory examples

// +
// ** src/main/scala/progscala2/fp/combinators/combinators.sc

object Combinators1 {
  def map[A,B](list: List[A])(f: (A) ⇒ B): List[B] = list map f
}

object Combinators {
  def map[A,B](f: (A) ⇒ B)(list: List[A]): List[B] = list map f
}

val intToString = (i:Int) => s"N=$i"
// Result: intToString: Int => String = <function1>

val flist = Combinators.map(intToString) _
// Result: flist: List[Int] => List[String] = <function1>

val list = flist(List(1, 2, 3, 4))
// Result: list: List[String] = List(N=1, N=2, N=3, N=4)

// +
// ** src/main/scala/progscala2/fp/combinators/payroll.sc

case class Employee (
  name: String,
  title: String,
  annualSalary: Double,
  taxRate: Double,
  insurancePremiumsPerWeek: Double)

val employees = List(
  Employee("<NAME>", "CEO", 200000, 0.25, 100.0),
  Employee("<NAME>", "CFO", 170000, 0.22, 120.0),
  Employee("<NAME>", "Developer", 130000, 0.20, 120.0))

// Calculate weekly payroll:
val netPay = employees map { e => 
  val net = (1.0 - e.taxRate) * (e.annualSalary / 52.0) - 
    e.insurancePremiumsPerWeek
  (e, net)
}

// "Print" paychecks:
println("** Paychecks:")
netPay foreach { 
  case (e, net) => println(f"  ${e.name+':'}%-16s ${net}%10.2f") 
}

// Generate report:
val report = (netPay foldLeft (0.0, 0.0, 0.0)) { 
  case ((totalSalary, totalNet, totalInsurance), (e, net)) => 
    (totalSalary + e.annualSalary/52.0, 
      totalNet + net, 
      totalInsurance + e.insurancePremiumsPerWeek)
}

println("\n** Report:")
println(f"  Total Salary:    ${report._1}%10.2f")
println(f"  Total Net:       ${report._2}%10.2f")
println(f"  Total Insurance: ${report._3}%10.2f")

// +
// ** src/main/scala/progscala2/fp/datastructs/seq.sc

val seq1 = Seq("Programming", "Scala")                               // <1>
val seq2 = "People" +: "should" +: "read" +: seq1                    // <2>

val seq3 = "Programming" +: "Scala" +: Seq.empty                     // <3>
val seq4 = "People" +: "should" +: "read" +: Seq.empty
val seq5 = seq4 ++ seq3                                              // <4>

// +
// ** src/main/scala/progscala2/fp/datastructs/fold-map.sc

(List(1, 2, 3, 4, 5, 6) foldRight List.empty[String]) {
  (x, list) => ("[" + x + "]") :: list
}

// +
// ** src/main/scala/progscala2/fp/datastructs/set.sc

val states = Set("Alabama", "Alaska", "Wyoming")

val lengths = states map (st => st.length)
println(lengths)

val states2 = states + "Virginia"
println(states2)

val states3 = states2 + ("New York", "Illinois")
println(states3)

// +
// ** src/main/scala/progscala2/fp/datastructs/filter.sc

val stateCapitals = Map(
  "Alabama" -> "Montgomery",
  "Alaska"  -> "Juneau",
  "Wyoming" -> "Cheyenne")
  
val map2 = stateCapitals filter { kv => kv._1 startsWith "A" }

println( map2 )

// +
// ** src/main/scala/progscala2/fp/datastructs/fibonacci.sc
import scala.math.BigInt

val fibs: Stream[BigInt] =
  BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map (n => n._1 + n._2)

fibs take 10 foreach (i => print(s"$i "))

// +
// ** src/main/scala/progscala2/fp/datastructs/map.sc

val stateCapitals = Map(
  "Alabama" -> "Montgomery",
  "Alaska"  -> "Juneau",
  "Wyoming" -> "Cheyenne")

val lengths = stateCapitals map { 
  kv => (kv._1, kv._2.length) 
}
println(lengths)

val caps = stateCapitals map { 
  case (k, v) => (k, v.toUpperCase)
}
println(caps)

val stateCapitals2 = stateCapitals + ("Virginia" -> "Richmond")
println(stateCapitals2)

val stateCapitals3 = stateCapitals2 + (
  "New York" -> "Albany", "Illinois" -> "Springfield")
println(stateCapitals3)

// +
// ** src/main/scala/progscala2/fp/datastructs/fold-impl.sc

// Simplified implementation. Does not output the actual collection type
// that was input as Seq[A].
def reduceLeft[A,B](s: Seq[A])(f: A => B): Seq[B] = {
  @annotation.tailrec
  def rl(accum: Seq[B], s2: Seq[A]): Seq[B] = s2 match {
    case head +: tail => rl(f(head) +: accum, tail)
    case _ => accum
  }
  rl(Seq.empty[B], s)
}

def reduceRight[A,B](s: Seq[A])(f: A => B): Seq[B] = s match {
  case head +: tail => f(head) +: reduceRight(tail)(f)
  case _ => Seq.empty[B]
}

val list = List(1,2,3,4,5,6)

reduceLeft(list)(i => 2*i)
// => List(12, 10, 8, 6, 4, 2)

reduceRight(list)(i => 2*i)

// +
// ** src/main/scala/progscala2/fp/datastructs/foreach.sc

List(1, 2, 3, 4, 5) foreach { i => println("Int: " + i) }

val stateCapitals = Map(
  "Alabama" -> "Montgomery",
  "Alaska"  -> "Juneau",
  "Wyoming" -> "Cheyenne")

//stateCapitals foreach { kv => println(kv._1 + ": " + kv._2) }
stateCapitals foreach { case (k, v) => println(k + ": " + v) }

// +
// ** src/main/scala/progscala2/fp/datastructs/list.sc

val list1 = List("Programming", "Scala")                             // <1>
val list2 = "People" :: "should" :: "read" :: list1                  // <2>

val list3 = "Programming" :: "Scala" :: Nil                          // <3>
val list4 = "People" :: "should" :: "read" :: Nil
val list5 = list4 ++ list3                                           // <4>

// +
// ** src/main/scala/progscala2/fp/datastructs/foldreduce.sc

List(1,2,3,4,5,6) reduce (_ + _)

List(1,2,3,4,5,6).fold (10) (_ * _)
(List(1,2,3,4,5,6) fold 10) (_ * _)

val fold1 = (List(1,2,3,4,5,6) fold 10) _
fold1(_ * _)

(List.empty[Int] fold 10) (_ + _)
try {
  List.empty[Int] reduce (_ + _)
} catch {
  case e: java.lang.UnsupportedOperationException => e
}

List.empty[Int] optionReduce (_ + _)
List(1,2,3,4,5,6) optionReduce (_ * _)

// +
// ** src/main/scala/progscala2/fp/datastructs/vector.sc

val vect1 = Vector("Programming", "Scala")
val vect2 = "People" +: "should" +: "read" +: vect1
println(vect2)

val vect3 = "Programming" +: "Scala" +: Vector.empty[String]
val vect4 = "People" +: "should" +: "read" +: Vector.empty[String]
val vect5 = vect4 ++ vect3
println(vect5)

// +
// ** src/main/scala/progscala2/fp/datastructs/flatmap.sc

val list = List("now", "is", "", "the", "time")

list flatMap (s => s.toList)

// Like flatMap, but flatMap eliminates the intermediate step!
val list2 = List("now", "is", "", "the", "time") map (s => s.toList)

// +
// ** src/main/scala/progscala2/fp/datastructs/fold-vector-impl.sc

// Specific implementation for Vectors where we append new elements,
// not prepend them like we did in fold-impl.sc for Seqs.
def reduceLeftV[A,B](s: Vector[A])(f: A => B): Vector[B] = {
  @annotation.tailrec
  def rl(accum: Vector[B], s2: Vector[A]): Vector[B] = s2 match {
    case head +: tail => rl(accum :+ f(head), tail)
    case _ => accum
  }
  rl(Vector.empty[B], s)
}

def reduceRightV[A,B](s: Vector[A])(f: A => B): Vector[B] = s match {
  case head +: tail => reduceRightV(tail)(f) :+ f(head)
  case _ => Vector.empty[B]
}

val vect = Vector(1,2,3,4,5,6)

reduceLeftV(vect)(i => 2*i)
// => Vector(2, 4, 6, 8, 10, 12)

reduceRightV(vect)(i => 2*i)

// +
// ** src/main/scala/progscala2/fp/datastructs/fold-assoc-funcs.sc

// fac: a func. that is associative and commutative.
// Define left and right versions, because reduceLeft takes the accumulator
// as the first argument, while reduceRight takes it as the second arg.
val facLeft  = (accum: Int, x: Int) => accum + x
val facRight = (x: Int, accum: Int) => accum + x

val list1 = List(1,2,3,4,5)

list1 reduceLeft  facLeft   // = 15
list1 reduceRight facRight  // = 15

// fnc: a func. that is associative but not commutative:
val fncLeft  = (accum: Int, x: Int) => accum - x
val fncRight = (x: Int, accum: Int) => accum - x

list1 reduceLeft  fncLeft   // = -13
list1 reduceRight fncRight  // = -5

// reduceLeft was this:
((((1 - 2) - 3) - 4) - 5)      // = -13
// reduceRight was this:
((((5 - 4) - 3) - 2) - 1)      // = -5
// or put another way, with the numbers in their original order:
(-1 + (-2 + (-3 + (-4 + 5))))  // = -5

// But "x - y" is associative if we note that "x - y" == "x + -y":
((((1 - 2) - 3) - 4) - 5)      // = -13
((((1 + -2) + -3) + -4) + -5)  // = -13
(1 + (-2 + (-3 + (-4 + -5))))  // = -13

// fnac: a func. that is neither associative nor commutative.
val fnacLeft  = (x: String, y: String) => s"($x)-($y)"
val fnacRight = (x: String, y: String) => s"($y)-($x)"

val list2 = list1 map (_.toString)

list2 reduceLeft  fnacLeft   // = ((((1)-(2))-(3))-(4))-(5)
list2 reduceRight fnacRight  // = ((((5)-(4))-(3))-(2))-(1)
list2 reduceRight fnacLeft   // = (1)-((2)-((3)-((4)-(5))))
// -

// #### For Comprehensions

// +
// ** src/main/scala/progscala2/forcomps/for-options-bad2.sc

val successfulCounts = Seq(Some(5), Some(21), Some(8))
val partiallySuccessfulCounts = Seq(Some(5), None, Some(8))

def sumCountsBad(counts: Seq[Option[Int]]): Option[Int] = 
  (counts foldLeft Option(0)) {
    (countOption, count) => 
      if (countOption == None || count == None) None
      else Some(countOption.get + count.get)
  }

sumCountsBad(successfulCounts)
sumCountsBad(partiallySuccessfulCounts)

// +
// ** src/main/scala/progscala2/forcomps/for-eithers-good.sc

def positive(i: Int): Either[String,Int] = 
  if (i > 0) Right(i) else Left(s"nonpositive number $i")

for {
  i1 <- positive(5).right
  i2 <- positive(10 * i1).right
  i3 <- positive(25 * i2).right
  i4 <- positive(2  * i3).right
} yield (i1 + i2 + i3 + i4)
// Returns: scala.util.Either[String,Int] = Right(3805)

for {
  i1 <- positive(5).right
  i2 <- positive(-1 * i1).right   // EPIC FAIL!
  i3 <- positive(25 * i2).right
  i4 <- positive(-2 * i3).right   // EPIC FAIL!
} yield (i1 + i2 + i3 + i4)
// Returns: scala.util.Either[String,Int] = Left(nonpositive number -5)

// +
// ** src/main/scala/progscala2/forcomps/for-eithers-seq.sc
import scala.util.{ Either, Left, Right }

val results: Seq[Either[RuntimeException,Int]] =
  Vector(Right(10), Left(new RuntimeException("boo!")), Right(20))

val results2a = for {
  Right(i) <- results
} yield (2 * i)
// Returns: results2a: Seq[Int] = Vector(20, 40)

// +
// ** src/main/scala/progscala2/forcomps/for-options-good.sc

def positive(i: Int): Option[Int] = 
  if (i > 0) Some(i) else None

for {
  i1 <- positive(5)
  i2 <- positive(10 * i1)
  i3 <- positive(25 * i2)
  i4 <- positive(2  * i3)
} yield (i1 + i2 + i3 + i4)
// Returns: Option[Int] = Some(3805)

for {
  i1 <- positive(5)
  i2 <- positive(-1 * i1)              // <1>   EPIC FAIL!
  i3 <- positive(25 * i2)              // <2>
  i4 <- positive(-2 * i3)              // EPIC FAIL!
} yield (i1 + i2 + i3 + i4)
// Returns: Option[Int] = None

// +
// ** src/main/scala/progscala2/forcomps/for-options-seq.sc

val results: Seq[Option[Int]] = Vector(Some(10), None, Some(20))

val results2a = for {
  Some(i) <- results
} yield (2 * i)
// Returns: results2a: Seq[Int] = Vector(20, 40)

// Translation step #1
val results2b = for {
  Some(i) <- results withFilter { 
    case Some(i) => true
    case None => false 
  }
} yield (2 * i)
// Returns: results2b: Seq[Int] = Vector(20, 40)

// Translation step #2
val results2c = results withFilter { 
  case Some(i) => true
  case None => false 
} map {
  case Some(i) => (2 * i)
}
// Returns: results2c: Seq[Int] = Vector(20, 40)

// +
// ** src/main/scala/progscala2/forcomps/for-validations-good.sc
import scalaz._, std.AllInstances._

def positive(i: Int): Validation[List[String], Int] = {
  if (i > 0) Success(i)                                              // <1>
  else Failure(List(s"Nonpositive integer $i"))
}

for {
  i1 <- positive(5)
  i2 <- positive(10 * i1)
  i3 <- positive(25 * i2)
  i4 <- positive(2  * i3)
} yield (i1 + i2 + i3 + i4)
// Returns: scalaz.Validation[List[String],Int] = Success(3805)

for {
  i1 <- positive(5)
  i2 <- positive(-1 * i1)              // EPIC FAIL!
  i3 <- positive(25 * i2)
  i4 <- positive(-2 * i3)              // EPIC FAIL!
} yield (i1 + i2 + i3 + i4)
// Returns: scalaz.Validation[List[String],Int] =
//   Failure(List(Nonpositive integer -5))                           // <2>

positive(5) +++ positive(10) +++ positive(25)                        // <3>
// Returns: scalaz.Validation[String,Int] = Success(40)

positive(5) +++ positive(-10) +++ positive(25) +++ positive(-30)     // <4>
// Returns: scalaz.Validation[String,Int] =
//   Failure(Nonpositive integer -10, Nonpositive integer -30)

// +
// ** src/main/scala/progscala2/forcomps/ref-transparency.sc

def addInts(s1: String, s2: String): Int = s1.toInt + s2.toInt

for {
  i <- 1 to 3
  j <- 1 to i
} println(s"$i+$j = ${addInts(i.toString, j.toString)}")

def addInts2(s1: String, s2: String): Either[NumberFormatException,Int] = 
  try { 
    Right(s1.toInt + s2.toInt)
  } catch { 
    case nfe: NumberFormatException => Left(nfe)
  }

println(addInts2("1", "2"))
println(addInts2("1", "x"))
println(addInts2("x", "2"))

// +
// ** src/main/scala/progscala2/forcomps/for-tries-seq.sc
import scala.util.{ Try, Success, Failure }

val results: Seq[Try[Int]] =
  Vector(Success(10), Failure(new RuntimeException("boo!")), Success(20))

val results2a = for {
  Success(i) <- results
} yield (2 * i)
// Returns: results2a: Seq[Int] = Vector(20, 40)

// +
// ** src/main/scala/progscala2/forcomps/for-variable-translated.sc

val map = Map("one" -> 1, "two" -> 2)

val list1 = for {
  (key, value) <- map   // How is this line and the next translated?
  i10 = value + 10
} yield (i10)
// Result: list1: scala.collection.immutable.Iterable[Int] = List(11, 12)

// Translation:
val list2 = for {
  (i, i10) <- for {
    x1 @ (key, value) <- map
  } yield {
    val x2 @ i10 = value + 10
    (x1, x2)
  } 
} yield (i10)

// +
// ** src/main/scala/progscala2/forcomps/for-flatmap.sc

val states = List("Alabama", "Alaska", "Virginia", "Wyoming")

for {
  s <- states
  c <- s
} yield s"$c-${c.toUpper}"
// Results: List("A-A", "l-L", "a-A", "b-B", ...)

states flatMap (_.toSeq map (c => s"$c-${c.toUpper}"))
// Results: List("A-A", "l-L", "a-A", "b-B", ...)

// +
// ** src/main/scala/progscala2/forcomps/for-validations-good-form.sc
import scalaz._, std.AllInstances._

/** Validate a user's name; nonempty and alphabetic characters, only. */
def validName(key: String, name: String):
    Validation[List[String], List[(String,Any)]] = {
  val n = name.trim  // remove whitespace
  if (n.length > 0 && n.matches("""^\p{Alpha}$""")) Success(List(key -> n))
  else Failure(List(s"Invalid $key <$n>"))
}

/** Validate that the string is an integer and greater than zero. */
def positive(key: String, n: String):
    Validation[List[String], List[(String,Any)]] = {
  try {
    val i = n.toInt
    if (i > 0) Success(List(key -> i))
    else Failure(List(s"Invalid $key $i"))
  } catch {
    case _: java.lang.NumberFormatException =>
      Failure(List(s"$n is not an integer"))
  }
}

def validateForm(firstName: String, lastName: String, age: String):
    Validation[List[String], List[(String,Any)]] = {
  validName("first-name", firstName) +++ validName("last-name", lastName) +++
    positive("age", age)
}

validateForm("Dean", "Wampler", "29")
// Returns: Success(List((first-name,Dean), (last-name,Wampler), (age,29)))
validateForm("", "Wampler", "29")
// Returns: Failure(List(Invalid first-name <>))
validateForm("D e a n", "Wampler", "29")
// Returns: Failure(List(Invalid first-name <D e a n>))
validateForm("D1e2a3n_", "Wampler", "29")
// Returns: Failure(List(Invalid first-name <D1e2a3n_>))
validateForm("Dean", "", "29")
// Returns: Failure(List(Invalid last-name <>))
validateForm("Dean", "Wampler", "0")
// Returns: Failure(List(Invalid age 0))
validateForm("Dean", "Wampler", "xx")
// Returns: Failure(List(xx is not an integer))
validateForm("", "Wampler", "0")
// Returns: Failure(List(Invalid first-name <>, Invalid age 0))
validateForm("Dean", "", "0")
// Returns: Failure(List(Invalid last-name <>, Invalid age 0))
validateForm("D e a n", "", "29")
// Returns: Failure(List(Invalid first-name <D e a n>, Invalid last-name <>))

// +
// ** src/main/scala/progscala2/forcomps/for-guard.sc

val states = List("Alabama", "Alaska", "Virginia", "Wyoming")

for {
  s <- states
  c <- s
  if c.isLower
} yield s"$c-${c.toUpper} "
// Results: List("l-L", "a-A", "b-B", ...)

states flatMap (_.toSeq withFilter (_.isLower) map (c => s"$c-${c.toUpper}"))
// Results: List("l-L", "a-A", "b-B", ...)

// +
// ** src/main/scala/progscala2/forcomps/for-options-bad.sc

// An ugly way to process a sequence of options where we stop 
// on the first None. Compare with the for-option-good.sc implementation.
// Note that the +return+ keyword is rarely used in Scala code.
// When you see one, treat it as a _design smell_, a possible candidate
// for refactoring. Often, it can be eliminated by decomposing a function
// into smaller functions.

def doThreeSteps(
    step1: Int => Option[Int], 
    step2: Int => Option[Int], 
    step3: Int => Option[Int]): Option[Int] = {
  val result1 = step1(0) match {
    case None => return None
    case Some(result) => result
  }
  val result2 = step2(result1) match {
    case None => return None
    case Some(result) => result
  }
  step3(result2)
}

doThreeSteps(
  i1 => Some(i1 + 5),
  i2 => Some(i2 + 10),
  i3 => Some(i3 + 25))
// Returns: Option[Int] = Some(40)

doThreeSteps(
  i1 => Some(i1 + 5),
  i2 => None,   // EPIC FAIL!
  i3 => Some(i3 + 25))
// Returns: Option[Int] = None

// +
// ** src/main/scala/progscala2/forcomps/for-tries-good.sc
import scala.util.{ Try, Success, Failure }

def positive(i: Int): Try[Int] = Try {
  assert (i > 0, s"nonpositive number $i")
  i
}

for {
  i1 <- positive(5)
  i2 <- positive(10 * i1)
  i3 <- positive(25 * i2)
  i4 <- positive(2  * i3)
} yield (i1 + i2 + i3 + i4)
// Returns: scala.util.Try[Int] = Success(3805)

for {
  i1 <- positive(5)
  i2 <- positive(-1 * i1)              // EPIC FAIL!
  i3 <- positive(25 * i2)
  i4 <- positive(-2 * i3)              // EPIC FAIL!
} yield (i1 + i2 + i3 + i4)
// Returns: scala.util.Try[Int] = Failure(
//   java.lang.AssertionError: assertion failed: nonpositive number -5)

// +
// ** src/main/scala/progscala2/forcomps/for-map.sc

val states = List("Alabama", "Alaska", "Virginia", "Wyoming")

for {
  s <- states
} yield s.toUpperCase
// Results: List(ALABAMA, ALASKA, VIRGINIA, WYOMING)

states map (_.toUpperCase)
// Results: List(ALABAMA, ALASKA, VIRGINIA, WYOMING)

// +
// ** src/main/scala/progscala2/forcomps/for-variable.sc

val states = List("Alabama", "Alaska", "Virginia", "Wyoming")

for {
  s <- states
  c <- s
  if c.isLower
  c2 = s"$c-${c.toUpper} "
} yield c2
// Results: List("l-L", "a-A", "b-B", ...)

states flatMap (_.toSeq withFilter (_.isLower) map { c => 
  val c2 = s"$c-${c.toUpper} "
  c2
})
// Results: List("l-L", "a-A", "b-B", ...)

// +
// ** src/main/scala/progscala2/forcomps/for-foreach.sc

val states = List("Alabama", "Alaska", "Virginia", "Wyoming")

for {
  s <- states
} println(s)
// Results:
// Alabama
// Alaska
// Virginia
// Wyoming

states foreach println
// Results the same as before.

// +
// ** src/main/scala/progscala2/forcomps/for-patterns.sc

val ignoreRegex = """^\s*(#.*|\s*)$""".r                             // <1>
val kvRegex = """^\s*([^=]+)\s*=\s*([^#]+)\s*.*$""".r                // <2>

val properties = """
  |# Book properties
  |
  |book.name = Programming Scala, Second Edition # A comment 
  |book.authors = <NAME> and <NAME>
  |book.publisher = O'Reilly
  |book.publication-year = 2014
  |""".stripMargin                                                   // <3>

val kvPairs = for {
  prop <- properties.split("\n")                                     // <4>
  if ignoreRegex.findFirstIn(prop) == None                           // <5>
  kvRegex(key, value) = prop                                         // <6>
} yield (key.trim, value.trim)                                       // <7>
// Returns: kvPairs: Array[(String, String)] = Array( 
//   (book.name,Programming Scala, Second Edition), 
//   (book.authors,<NAME> and <NAME>), 
//   (book.publisher,O'Reilly), 
//   (book.publication-year,2014))
// -

// #### Categories

// +
// ** src/main/scala/progscala2/fp/categories/for-options-steps.sc

// Example of using option handling on an arbitrarily-long 
// sequence of functions that return Option[Int]. When the sequence
// is arbitrary, you can't use a for comprehension.
  
// Alias the function signature:
type Step = Int => Option[Int]

// A sequence of functions for the process steps. Each takes the result of
// the previous step (or a seed value to start) and returns a new option.
val successfulSteps = List(
  (i:Int) => Some(i + 5), 
  (i:Int) => Some(i + 10), 
  (i:Int) => Some(i + 25))

val partiallySuccessfulSteps = List(
  (i:Int) => Some(i + 5), 
  // A step that fails and indicates the failure by returning +None+.
  (i:Int) => None,   // indicates a fail, not a compiler error.
  (i:Int) => Some(i + 25))

// Fold over the steps, starting with the seed value +0+, wrapped in a +Some+.
// Use +flatMap+ to extract the current sum value and pass it to the current 
// step, which returns a new +Option+ that becomes the next +sumOpt+. 
// If +sumOpt+ is actually a +None+, the anonymous function isn't called and
// // +None+ is returned.
def sumCounts1(countSteps: Seq[Step]): Option[Int] = 
  (countSteps foldLeft Option(0)) {
    (sumOpt, step) => sumOpt flatMap (i => step(i))
  }

sumCounts1(successfulSteps)
// Returns: Option[Int] = Some(40)

sumCounts1(partiallySuccessfulSteps)
// Returns: Option[Int] = None

// More verbose, but it stops the "counts" iteration at the first None
// and it doesn't create intermediate Options:
// We sequence through the steps and sum the values returned. 
// A nested, tail-recursive function +sum+ is used.
def sumCounts2(countSteps: Seq[Step]): Option[Int] = {
  @annotation.tailrec
  def sum(accum: Int, countSteps2: Seq[Step]): Option[Int] = 
    countSteps2 match {
      // Terminate the recursion when we hit the end of the steps +Seq+.
      case Nil          => Some(accum)
      // Otherwise, the head of the list is a step, so we call it
      // with the current +accum+ value. We pattern match on the 
      // returned +Option+.
      case step +: tail => step(accum) match {
        // If +None+, terminate the recursion and return +None+.
        case None     => None
        // Otherwise, call +sum+ again with the new accumulated value 
        // and the steps tail.
        case Some(i2) => sum(i2, tail)
      }
    } 
  // Call the nested function with the seed sum +0+ and the sequence of steps.
  sum(0, countSteps)
}

sumCounts2(successfulSteps)
// Returns: Option[Int] = Some(40)

sumCounts2(partiallySuccessfulSteps)
// Returns: Option[Int] = None

// +
// ** src/main/scala/progscala2/fp/categories/for-eithers-steps.sc
import scala.util.{ Either, Left, Right }

// Example of using Either handling on an arbitrarily-long
// sequence of functions that return Either[X,Int]. When the sequence
// is arbitrary, you can't use a for comprehension.

// Alias the long function signature:
type Step = Int => Either[RuntimeException,Int]

val successfulSteps: Seq[Step] = List(
  (i:Int) => Right(i + 5),
  (i:Int) => Right(i + 10),
  (i:Int) => Right(i + 25))
val partiallySuccessfulSteps: Seq[Step] = List(
  (i:Int) => Right(i + 5),
  (i:Int) => Left(new RuntimeException("FAIL!")),
  (i:Int) => Right(i + 25))

def sumCounts1(countSteps: Seq[Step]): Either[RuntimeException,Int] = {
  val zero: Either[RuntimeException,Int] = Right(0)
  (countSteps foldLeft zero) {
    (sumEither, step) => sumEither.right flatMap (i => step(i))
  }
}

sumCounts1(successfulSteps)
// Returns: .Either[RuntimeException,Int] = Right(40)

sumCounts1(partiallySuccessfulSteps)
// Returns: Either[RuntimeException,Int] = Left(RuntimeException: FAIL!)

// More verbose, but it stops the "counts" iteration at the first Left.
// and it doesn't create intermediate Rights:
def sumCounts2(countSteps: Seq[Step]): Either[RuntimeException,Int] = {
  @annotation.tailrec
  def sum(accum: Int, countSteps2: Seq[Step]): Either[RuntimeException,Int] =
    countSteps2 match {
      case Nil          => Right(accum)
      case step +: tail => step(accum) match {
        case l @ Left(x) => l
        case Right(i2)   => sum(i2, tail)
      }
    }
  sum(0, countSteps)
}

sumCounts2(successfulSteps)
// Returns: .Either[RuntimeException,Int] = Right(40)

sumCounts2(partiallySuccessfulSteps)
// Returns: Either[RuntimeException,Int] = Left(RuntimeException: FAIL!)

// +
// ** src/main/scala/progscala2/fp/categories/Functor.sc
import progscala2.fp.categories._
import scala.language.higherKinds

val fii: Int => Int       = i => i * 2
val fid: Int => Double    = i => 2.1 * i
val fds: Double => String = d => d.toString

SeqF.map(List(1,2,3,4))(fii)                // Seq[Int]: List(2, 4, 6, 8)
SeqF.map(List.empty[Int])(fii)              // Seq[Int]: List()

OptionF.map(Some(2))(fii)                   // Option[Int]: Some(4)
OptionF.map(Option.empty[Int])(fii)         // Option[Int]: None

val fa = FunctionF.map(fid)(fds)                                     // <1>
fa(2)                                       // String: 4.2

// val fb = FunctionF.map(fid)(fds)                                     <2>
val fb = FunctionF.map[Int,Double,String](fid)(fds)
fb(2)

val fc = fds compose fid                                             // <3>
fc(2)                                       // String: 4.2

// +
// ** src/main/scala/progscala2/fp/categories/for-tries-steps.sc
import scala.util.{ Try, Success, Failure }

// Example of using try handling on an arbitrarily-long
// sequence of functions that return Try[Int]. When the sequence
// is arbitrary, you can't use a for comprehension.

// Alias the function signature:
type Step = Int => Try[Int]

val successfulSteps: Seq[Step] = List(
  (i:Int) => Success(i + 5),
  (i:Int) => Success(i + 10),
  (i:Int) => Success(i + 25))
val partiallySuccessfulSteps: Seq[Step] = List(
  (i:Int) => Success(i + 5),
  (i:Int) => Failure(new RuntimeException("FAIL!")),
  (i:Int) => Success(i + 25))

def sumCounts1(countSteps: Seq[Step]): Try[Int] = {
  val zero: Try[Int] = Success(0)
  (countSteps foldLeft zero) {
    (sumTry, step) => sumTry flatMap (i => step(i))
  }
}

sumCounts1(successfulSteps)
// Returns: scala.util.Try[Int] = Success(40)

sumCounts1(partiallySuccessfulSteps)
// Returns: scala.util.Try[Int] = Failure(java.lang.RuntimeException: FAIL!)

// More verbose, but it stops the "counts" iteration at the first Failure.
// and it doesn't create intermediate Successes:
def sumCounts2(countSteps: Seq[Step]): Try[Int] = {
  @annotation.tailrec
  def sum(accum: Int, countSteps2: Seq[Step]): Try[Int] =
    countSteps2 match {
      case Nil          => Success(accum)
      case step +: tail => step(accum) match {
        case f @ Failure(ex) => f
        case Success(i2)     => sum(i2, tail)
      }
    }
  sum(0, countSteps)
}

sumCounts2(successfulSteps)
// Returns: scala.util.Try[Int] = Success(40)

sumCounts2(partiallySuccessfulSteps)
// Returns: scala.util.Try[Int] = Failure(java.lang.RuntimeException: FAIL!)

// +
// ** src/main/scala/progscala2/fp/categories/Monad.sc
import progscala2.fp.categories._
import scala.language.higherKinds

val seqf: Int => Seq[Int] = i => 1 to i
val optf: Int => Option[Int] = i => Option(i + 1)

SeqM.flatMap(List(1,2,3))(seqf)             // Seq[Int]: List(1,1,2,1,2,3)
SeqM.flatMap(List.empty[Int])(seqf)         // Seq[Int]: List()

OptionM.flatMap(Some(2))(optf)              // Option[Int]: Some(3)
OptionM.flatMap(Option.empty[Int])(optf)    // Option[Int]: None
// -

// ## Implicits
//
// Using implicit methods

// +
// ** src/main/scala/progscala2/implicits/implicit-conversions-resolution.sc
import scala.language.implicitConversions

// WARNING: You must :paste mode in the REPL for the following.
// Using :load won't compile the two definitions together!
case class Foo(s: String)
object Foo {
  implicit def fromString(s: String): Foo = Foo(s)
}

class O {
  def m1(foo: Foo) = println(foo)
  def m(s: String) = m1(s)
}

// +
// ** src/main/scala/progscala2/implicits/implicit-conversions-resolution2.sc

// To enable scala's own implicit conversions, it needs to be imported
import scala.language.implicitConversions

case class Foo(s: String)

object Foo {
  implicit def fromString(s: String): Foo = Foo(s)
}

implicit def overridingConversion(s: String): Foo = Foo("Boo: " + s)

class O {
  def m1(foo: Foo) = println(foo)
  def m(s: String) = m1(s)
}

// +
val f1 = Foo("a Foo")
val o1 = new O()

o1.m1(f1)
o1.m("a bit of Boo")

// + tags=[]
// ** src/main/scala/progscala2/implicits/implicit-args.sc

// This demonstrates how one can pass an implicit argument to a method.

// Define a method that takes an implicit argument
// Note: never use Floats for money
def calcTax(amount: Float)(implicit rate: Float): Float = amount * rate

// Define some objects that define something as implicit

// Here it is a member variable
object SimpleStateSalesTax {
  implicit val rate: Float = 0.05F
}

// Here it is a class with more data for the object
case class ComplicatedSalesTaxData(
  baseRate: Float,
  isTaxHoliday: Boolean,
  storeId: Int)

// Here is the second object, it provides the implicit rate and uses an implicit class.
object ComplicatedSalesTax {
  private def extraTaxRateForStore(id: Int): Float = {
    // From id, determine location, then extra taxes...
    0.0F
  }

  implicit def rate(implicit cstd: ComplicatedSalesTaxData): Float = 
    if (cstd.isTaxHoliday) 0.0F
    else cstd.baseRate + extraTaxRateForStore(cstd.storeId)
}

// The first example is straightforward
// Import the implicit and it will is found by the calcTax function
{
  import SimpleStateSalesTax.rate

  val amount = 100F
  println(s"Tax on $amount = ${calcTax(amount)}")
}

// This is more subtle, import the function, define an object with more properties and that will
// be used as the implicit
{
  import ComplicatedSalesTax.rate
  implicit val myStore = ComplicatedSalesTaxData(0.06F, false, 1010)

  val amount = 100F
  println(s"Tax on $amount = ${calcTax(amount)}")
}
// -

// ### JSON Representation

// +
// ** src/main/scala/progscala2/implicits/toJSON-type-class.sc

// Using an implicit class it is possible to bind to a class specific handler

case class Address(street: String, city: String)
case class Person(name: String, address: Address)

// Define an interface for a general method as a trait
trait ToJSON {
  def toJSON(level: Int = 0): String

  val INDENTATION = "  "
  def indentation(level: Int = 0): (String,String) = 
    (INDENTATION * level, INDENTATION * (level+1))
}

// Add a class specific implicit for the case classes that has a class-specific implementation

implicit class AddressToJSON(address: Address) extends ToJSON {
  def toJSON(level: Int = 0): String = {
    val (outdent, indent) = indentation(level)
    s"""{
      |${indent}"street": "${address.street}", 
      |${indent}"city":   "${address.city}"
      |$outdent}""".stripMargin
  }
}

implicit class PersonToJSON(person: Person) extends ToJSON {
  def toJSON(level: Int = 0): String = {
    val (outdent, indent) = indentation(level)
    s"""{
      |${indent}"name":    "${person.name}", 
      |${indent}"address": ${person.address.toJSON(level + 1)} 
      |$outdent}""".stripMargin
  }
}

val a = Address("1 Scala Lane", "Anytown")
val p = Person("<NAME>", a)

// Then the runtime binds the implicit class and finds the right toJSON method.
println(a.toJSON())
println()
println(p.toJSON())

// + tags=[]
// ** src/main/scala/progscala2/implicits/type-classes-subtyping2.sc
// Adapted from: src/main/scala/progscala2/implicits/toJSON-type-class.sc

case class Address(street: String, city: String, state: String, zip: String)
case class Person(name: String, age: Int, address: Address)

trait ToJSON[+T] {
  def toJSON(level: Int = 0): String

  val INDENTATION = "  "
  def indentation(level: Int = 0): (String,String) =
    (INDENTATION * level, INDENTATION * (level+1))
}

implicit class AddressToJSON(address: Address) extends ToJSON[Address] {
  def toJSON(level: Int = 0): String = {
    val (outdent, indent) = indentation(level)
    s"""{
      |${indent}"street": "${address.street}",
      |${indent}"city":   "${address.city}",
      |${indent}"state":  "${address.state}",
      |${indent}"zip":    "${address.zip}"
      |$outdent}""".stripMargin
  }
}

implicit class PersonToJSON(person: Person) extends ToJSON[Person] {
  def toJSON(level: Int = 0): String = {
    val (outdent, indent) = indentation(level)
    s"""{
      |${indent}"name":    "${person.name}",
      |${indent}"age":     "${person.age}",
      |${indent}"address": ${new AddressToJSON(person.address).toJSON(level+1)}
      |$outdent}""".stripMargin
  }
}

// +
val a = Address("1 Scala Lane", "Anytown", "CA", "98765")

// We want to use this list:
val list1 = List(a, Person("<NAME>", 29, a))
// -

// But this attempt to convert to JSON, and other variations, don't work:
list1 map _.toJSON()

// This works, but it's ugly and the list has the *ToJSON objects, not
// the original Address and Person:
val list2: List[ToJSON[_]] = List(a, Person("<NAME>", 29, a))
list2 map ((x: ToJSON[_]) => x.toJSON())

// So, does this work?
implicit class ToJSONs[+T : ToJSON](seq: Seq[T]) {
  def apply(): Seq[String] =
    seq map (t => implicitly[ToJSON[T]].toJSON())
}

// NO: It complains about that it can't find a matching typeclass instance
// with toJSON.
println(list1.toJSON())

// Does this ugly hack fix it?
implicit class GodToJSON(x: Any) extends ToJSON[Any] {
  def toJSON(level: Int = 0): String = x match {
    case person: Person   => new PersonToJSON(person).toJSON(level)
    case address: Address => new AddressToJSON(address).toJSON(level)
    case other => throw new UnsupportedOperationException(
      s"No toJSON type class instance is available for $other")
  }
}

// NO: We still get the same error.
ToJSONs(list1)

// This finally works...
list1 map ((x: Any) => x.toJSON())
// But it only worked because we used this expression in the PersonToJSON,
// ${new AddressToJSON(person.address).toJSON(level+1)}, rather than
// ${person.address.toJSON(level+1)}. For the later, the GodToJSON isn't
// "found" and the "other" exception is thrown when we try to process the
// Person instance.

// ### Implicitly and Advanced Features

// +
// ** src/main/scala/progscala2/implicits/implicitly-args.sc

// This demonstrates the use of the implicitly class: https://www.baeldung.com/scala/implicitly

// https://www.scala-lang.org/api/current/scala/math/Ordering.html
import math.Ordering

case class MyList[A](list: List[A]) {
  def sortBy1[B](f: A => B)(implicit ord: Ordering[B]): List[A] =
    list.sortBy(f)(ord)

  def sortBy2[B : Ordering](f: A => B): List[A] =
    list.sortBy(f)(implicitly[Ordering[B]])
}

val list = MyList(List(1,3,5,2,4))

list sortBy1 (i => -i)
list sortBy2 (i => -i)

list sortBy1 (i => i)
list sortBy2 (i => i)

// +
// ** src/main/scala/progscala2/implicits/type-classes-subtyping.sc

trait Stringizer[+T] {
  def stringize: String
}

implicit class AnyStringizer(a: Any) extends Stringizer[Any] {
  def stringize: String = a match {
    case s: String => s
    case i: Int => (i*10).toString
    case f: Float => (f*10.1).toString
    case other => 
      throw new UnsupportedOperationException(s"Can't stringize $other")
  }
}

val list: List[Any] = List(1, 2.2F, "three", 'symbol)

list foreach { (x:Any) => 
  try {
    println(s"$x: ${x.stringize}")
  } catch {
    case e: java.lang.UnsupportedOperationException => println(e)
  }
}

// +
// ** src/main/scala/progscala2/implicits/implicit-erasure.sc
object M {
  implicit object IntMarker                                          // <1>
  implicit object StringMarker

  def m(seq: Seq[Int])(implicit i: IntMarker.type): Unit =           // <2>
    println(s"Seq[Int]: $seq")

  def m(seq: Seq[String])(implicit s: StringMarker.type): Unit =     // <3>
    println(s"Seq[String]: $seq")
}

import M._                                                           // <4>
m(List(1,2,3))
m(List("one", "two", "three"))

// +
// ** src/main/scala/progscala2/implicits/implicit-conversions.sc

// Adding operators

object Implicits {
  implicit final class DarthVadarShip[A](val self: A) {
    def <-*-> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
  }
}

// +
import Implicits._

val l = "three"
val r = 3

val t = l <-*-> r

// The operator doesn't work with strings - some operator precedence issue
// val m = Map( "one" <-*-> 1, "two" <-*-> 2)
val m = Map(("one") <-*-> 1, ("two") <-*-> 2)

// +
// ** src/main/scala/progscala2/implicits/custom-string-interpolator.sc

// This package is not available any more
// import scala.util.parsing.json._
// This will have to do
case class JSONObject(map: Map[String, Any])

// StringContext is that feature of scala that allows for string interpolation
// This is the feature that supports ${name} and so on

// Any* is the Scala version of varargs

object Interpolators {
  implicit class jsonForStringContext(val sc: StringContext) {       // <1>
    def json(values: Any*): JSONObject = {                           // <2>
      val keyRE = """^[\s{,]*(\S+):\s*""".r                          // <3>
      val keys = sc.parts map {                                      // <4>
        case keyRE(key) => key
        case str => str
      }
      val kvs = keys zip values                                      // <5>
      JSONObject(kvs.toMap)                                          // <6>
    }
  }
}

// +
// This is an unusual syntax, no space between json and the string that follows it.
// It may fail if other implicits get involved

import Interpolators._

val name = "<NAME>"
val book = "Programming Scala, Second Edition"

val jsonobj = json"{name: $name, book: $book}"                       // <7>
println(jsonobj)
// -

// ## Type System

// ### Abstract Types

// + tags=[]
// ** src/main/scala/progscala2/typesystem/abstracttypes/abstract-types-ex.sc

trait exampleTrait {
  type t1               // t1 is unconstrained
  type t2 >: t3 <: t1   // t2 must be a supertype of t3 and a subtype of t1
  type t3 <: t1         // t3 must be a subtype of t1
  type t4 <: Seq[t1]    // t4 must be a subtype of Seq of t1
  // type t5 = +AnyRef  // ERROR: Can't use variance annotations

  val v1: t1            // Can't initialize with value until t1 defined.
  val v2: t2            // ditto...
  val v3: t3            // ...
  val v4: t4            // ...
}

trait T1 { val name1: String }
trait T2 extends T1 { val name2: String }
case class C(name1: String, name2: String) extends T2

object example extends exampleTrait {
  type t1 = T1
  type t2 = T2
  type t3 = C
  type t4 = Vector[T1]

  val v1 = new T1 { val name1 = "T1"}
  val v2 = new T2 { val name1 = "T1"; val name2 = "T2" }
  val v3 = C("1", "2")
  val v4 = Vector(C("3", "4"))
}

// + [markdown] tags=[]
// ### Erasures and Other Features

// +
// ** src/main/scala/progscala2/typesystem/existentials/type-erasure-workaround.sc

object Doubler {
  def double(seq: Seq[_]): Seq[Int] = seq match {
    case Nil => Nil
    case head +: tail => (toInt(head) * 2) +: double(tail)
  }

  private def toInt(x: Any): Int = x match {
    case i: Int => i
    case s: String => s.toInt
    case x => throw new RuntimeException(s"Unexpected list element $x")
  }
}

// +
// ** src/main/scala/progscala2/typesystem/higherkinded/add-seq.sc
import progscala2.typesystem.higherkinded.Add              // <1>
import progscala2.typesystem.higherkinded.Add._

def sumSeq[T : Add](seq: Seq[T]): T =                      // <2>
  seq reduce (implicitly[Add[T]].add(_,_))

sumSeq(Vector(1 -> 10, 2 -> 20, 3 -> 30))                  // Result: (6,60)
sumSeq(1 to 10)                                            // Result: 55
sumSeq(Option(2))                                          // <3> Error! 

// +
// ** src/main/scala/progscala2/typesystem/higherkinded/add.sc
import scala.language.higherKinds
import progscala2.typesystem.higherkinded.{Add, Reduce}    // <1>
import progscala2.typesystem.higherkinded.Add._
import progscala2.typesystem.higherkinded.Reduce._

def sum[T : Add, M[T]](container: M[T])(                   // <2>
  implicit red: Reduce[T,M]): T =
    red.reduce(container)(implicitly[Add[T]].add(_,_))

sum(Vector(1 -> 10, 2 -> 20, 3 -> 30))                     // Result: (6,60)
sum(1 to 10)                                               // Result: 55
sum(Option(2))                                             // Result: 2
sum[Int,Option](None)                                      // <3> ERROR!

// +
// ** src/main/scala/progscala2/typesystem/higherkinded/add1.sc
import scala.language.higherKinds
import progscala2.typesystem.higherkinded.{Add, Reduce1}
import progscala2.typesystem.higherkinded.Add._
import progscala2.typesystem.higherkinded.Reduce1._

def sum[T : Add, M[_] : Reduce1](container: M[T]): T =
    implicitly[Reduce1[M]].reduce(container)(implicitly[Add[T]].add(_,_))

sum(Vector(1 -> 10, 2 -> 20, 3 -> 30))                     // Result: (6,60)
sum(1 to 10)                                               // Result: 55
sum(Option(2))                                             // Result: 2
sum[Int,Option](None)                                      // <4> ERROR!

// +
// * @author weaves

// * Stream is a form of Lazy List

// And is due to be deprecated.
// The Stream implemented in Java is similar.

// +
// ** src/main/scala/progscala2/typesystem/lazy/lazy-ints.sc

def from(n: Int): Stream[Int] = Stream.cons(n, from(n+1))

lazy val ints = from(0)
lazy val odds = ints.filter(_ % 2 == 1)
lazy val evens = ints.filter(_ % 2 == 0)

odds.take(10).print
evens.take(10).print

// +
// ** src/main/scala/progscala2/typesystem/lazy/lazy-fibonacci.sc

lazy val fib: Stream[Int] = 
  Stream.cons(0, Stream.cons(1, fib.zip(fib.tail).map(p => p._1 + p._2)))
  
fib.take(10).print

// +
// * @author weaves

// * Introductory examples

// +
// ** src/main/scala/progscala2/typesystem/abstracttypes/abstract-types-ex.sc

trait exampleTrait {
  type t1               // t1 is unconstrained
  type t2 >: t3 <: t1   // t2 must be a supertype of t3 and a subtype of t1
  type t3 <: t1         // t3 must be a subtype of t1
  type t4 <: Seq[t1]    // t4 must be a subtype of Seq of t1
  // type t5 = +AnyRef  // ERROR: Can't use variance annotations

  val v1: t1            // Can't initialize with value until t1 defined.
  val v2: t2            // ditto...
  val v3: t3            // ...
  val v4: t4            // ...
}

trait T1 { val name1: String }
trait T2 extends T1 { val name2: String }
case class C(name1: String, name2: String) extends T2

object example extends exampleTrait {
  type t1 = T1
  type t2 = T2
  type t3 = C
  type t4 = Vector[T1]

  val v1 = new T1 { val name1 = "T1"}
  val v2 = new T2 { val name1 = "T1"; val name2 = "T2" }
  val v3 = C("1", "2")
  val v4 = Vector(C("3", "4"))
}

// +
// * @author weaves

// * Introductory examples

// +
// * @author weaves

// * Functors

// This uses
// src/main/scala/progscala2/typesystem/typelambdas/Functor.scala

// 

// +
// ** src/main/scala/progscala2/typesystem/typelambdas/Functor.sc
import scala.language.higherKinds
import progscala2.typesystem.typelambdas.Functor._

List(1,2,3) map2 (_ * 2)               // List(2, 4, 6)

Option(2) map2 (_ * 2)                 // Some(4)

val m = Map("one" -> 1, "two" -> 2, "three" -> 3)

m map2 (_ * 2)                         // Map(one -> 2, two -> 4, three -> 6)

// +
// *** Note 
// This is an advanced use of implicits.
// -

// ### Bounds

// +
// ** src/main/scala/progscala2/typesystem/bounds/list/list-ab.sc
import progscala2.typesystem.bounds.list._

val languages = AbbrevList("Scala", "Java", "Ruby", "C#", "C++", "Python")
val list = 3.14 :: languages
println(list)

// +
// ** src/main/scala/progscala2/typesystem/bounds/view-bounds.sc
import scala.language.implicitConversions

object Serialization {
  case class Writable(value: Any) {
    def serialized: String = s"-- $value --"                         // <1>
  }

  implicit def fromInt(i: Int) = Writable(i)                         // <2>
  implicit def fromFloat(f: Float) = Writable(f)
  implicit def fromString(s: String) = Writable(s)
}

import Serialization._

object RemoteConnection {                                            // <3>
  def write[T <% Writable](t: T): Unit =                             // <4>
    println(t.serialized)  // Use stdout as the "remote connection"
}

RemoteConnection.write(100)       // Prints -- 100 --
RemoteConnection.write(3.14f)     // Prints -- 3.14 --
RemoteConnection.write("hello!")  // Prints -- hello! --
// RemoteConnection.write((1, 2))                                       <5>

// +
// ** src/main/scala/progscala2/typesystem/bounds/list/list-ab.sc
import progscala2.typesystem.bounds.list._

val languages = AbbrevList("Scala", "Java", "Ruby", "C#", "C++", "Python")
val list = 3.14 :: languages
println(list)

// +
// ** src/main/scala/progscala2/typesystem/bounds/lower-bounds.sc

class Parent(val value: Int) {                   // <1>
  override def toString = s"${this.getClass.getName}($value)" 
}
class Child(value: Int) extends Parent(value)

val op1: Option[Parent] = Option(new Child(1))   // <2>     Some(Child(1))
val p1: Parent = op1.getOrElse(new Parent(10))   // Result: Child(1)

val op2: Option[Parent] = Option[Parent](null)   // <3>     None
val p2a: Parent = op2.getOrElse(new Parent(10))  // Result: Parent(10)
val p2b: Parent = op2.getOrElse(new Child(100))  // Result: Child(100)

val op3: Option[Parent] = Option[Child](null)    // <4>     None
val p3a: Parent = op3.getOrElse(new Parent(20))  // Result: Parent(20)
val p3b: Parent = op3.getOrElse(new Child(200))  // Result: Child(200)

// +
// ** src/main/scala/progscala2/typesystem/bounds/lower-bounds2.sc

// Won't compile, because if +A is used, then "default" in +getOrElse+
// must be a of type B, where B >: A.
// case class Opt[+A](value: A = null) {
//   def getOrElse(default: A): A = if (value != null) value else default 
// }

case class Opt[A](value: A = null) {
  def getOrElse(default: A): A = if (value != null) value else default 
}

class Parent(val value: Int) {                             // <1>
  override def toString = s"${this.getClass.getName}($value)" 
}
class Child(value: Int) extends Parent(value)

val p4: Parent = Opt(new Child(1)).getOrElse(new Parent(10))
val p5: Parent = Opt[Parent](null).getOrElse(new Parent(10))
val p6: Parent = Opt[Child](null).getOrElse(new Parent(10))

// +
// ** src/main/scala/progscala2/typesystem/bounds/view-to-context-bounds.sc
import scala.language.implicitConversions

object Serialization {
  case class Rem[A](value: A) {
    def serialized: String = s"-- $value --"
  }
  type Writable[A] = A => Rem[A]                                     // <1>
  implicit val fromInt: Writable[Int]       = (i: Int)    => Rem(i)
  implicit val fromFloat: Writable[Float]   = (f: Float)  => Rem(f)
  implicit val fromString: Writable[String] = (s: String) => Rem(s)
}

import Serialization._

object RemoteConnection {
  def write[T : Writable](t: T): Unit =                              // <2>
    println(t.serialized)  // Use stdout as the "remote connection"
}

RemoteConnection.write(100)       // Prints -- 100 --                   <3>
RemoteConnection.write(3.14f)     // Prints -- 3.14 --
RemoteConnection.write("hello!")  // Prints -- hello! --
// RemoteConnection.write((1, 2))

// +
// * @author weaves

// * Introductory examples

// +
// ** src/main/scala/progscala2/typesystem/recursivetypes/f-bound.sc

trait Parent[T <: Parent[T]] {                                       // <1>
  def make: T 
}

case class Child1(s: String) extends Parent[Child1] {                // <2>
  def make: Child1 = Child1(s"Child1: make: $s")
}

case class Child2(s: String) extends Parent[Child2] {
  def make: Child2 = Child2(s"Child2: make: $s")
}

val c1  = Child1("c1")            // c1: Child1 = Child1(c1)
val c2  = Child2("c2")            // c2: Child2 = Child2(c2)
val c11 = c1.make                 // c11: Child1 = Child1(Child1: make: c1)
val c22 = c2.make                 // c22: Child2 = Child2(Child2: make: c2)

val p1: Parent[Child1] = c1       // p1: Parent[Child1] = Child1(c1)
val p2: Parent[Child2] = c2       // p2: Parent[Child2] = Child2(c2)
val p11 = p1.make                 // p11: Child1 = Child1(Child1: make: c1)
val p22 = p2.make                 // p22: Child2 = Child2(Child2: make: c2)

// + [markdown] tags=[]
// ### Collections

// +
// ** src/main/scala/progscala2/collections/parallel.sc

((1 to 10) fold "") ((s1, s2) => s"$s1 - $s2")
// Result: " - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10"

((1 to 10).par fold "") ((s1, s2) => s"$s1 - $s2")
// Results for two runs:
// " - 1 -  - 2 -  - 3 - 4 - 5 -  - 6 -  - 7 -  - 8 -  - 9 -  - 10"
// " - 1 -  - 2 -  - 3 -  - 4 - 5 -  - 6 -  - 7 -  - 8 - 9 - 10"

((1 to 10) fold 0) ((s1, s2) => s1 + s2)
// Result: 55

((1 to 10).par fold 0) ((s1, s2) => s1 + s2)
// Result: 55

// +
// ** src/main/scala/progscala2/collections/safeseq/safeseq.sc

val mutableSeq1: Seq[Int] = List(1,2,3,4)
val mutableSeq2: Seq[Int] = Array(1,2,3,4)

import progscala2.collections.safeseq._

val immutableSeq1: Seq[Int] = List(1,2,3,4)
val immutableSeq2: Seq[Int] = Array(1,2,3,4)


// +
// ** src/main/scala/progscala2/collections/ListBuilder.sc
import collection.mutable.Builder

class ListBuilder[T] extends Builder[T, List[T]] {

  private var storage = Vector.empty[T]

  def +=(elem: T) = {
    storage = storage :+ elem
    this
  }

  def clear(): Unit = { storage = Vector.empty[T] }

  def result(): List[T] = storage.toList
}

val lb = new ListBuilder[Int]
(1 to 3) foreach (i => lb += i)
lb.result
// Result: List(1, 2, 3)
// -

// ### Dependent Types and Miscellany

// +
// ** src/main/scala/progscala2/typesystem/dependentmethodtypes/dep-method.sc
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

case class LocalResponse(statusCode: Int)
case class RemoteResponse(message: String)

sealed trait Computation {
  type Response
  val work: Future[Response]
}

case class LocalComputation(
    work: Future[LocalResponse]) extends Computation {
  type Response = LocalResponse
}
case class RemoteComputation(
    work: Future[RemoteResponse]) extends Computation {
  type Response = RemoteResponse
}

object Service {
  def handle(computation: Computation): computation.Response = {
    val duration = Duration(2, SECONDS)
    Await.result(computation.work, duration)
  }
}

// +
// *** Activate

Service.handle(LocalComputation(Future(LocalResponse(0))))
// Result: LocalResponse = LocalResponse(0)

Service.handle(RemoteComputation(Future(RemoteResponse("remote call"))))
// Result: RemoteResponse = RemoteResponse(remote call)
// -

// ## Application Design

// +
// ** src/main/scala/progscala2/appdesign/dbc/BankAccount.sc

case class Money(val amount: Double) {                               // <1>
  require(amount >= 0.0, s"Negative amount $amount not allowed")

  def +  (m: Money): Money = Money(amount + m.amount)
  def -  (m: Money): Money = Money(amount - m.amount)
  def >= (m: Money): Boolean = amount >= m.amount

  override def toString = "$"+amount
}

case class BankAccount(balance: Money) {

  def debit(amount: Money) = {                                       // <2>
    assert(balance >= amount, 
      s"Overdrafts are not permitted, balance = $balance, debit = $amount")
    new BankAccount(balance - amount)
  }
  
  def credit(amount: Money) = {                                      // <3>
    new BankAccount(balance + amount)
  }
}

import scala.util.Try

Seq(-10, 0, 10) foreach (i => println(f"$i%3d: ${Try(Money(i))}"))

val ba1 = BankAccount(Money(10.0))
val ba2 = ba1.credit(Money(5.0))
val ba3 = ba2.debit(Money(8.5))
val ba4 = Try(ba3.debit(Money(10.0)))

println(s"""
  |Initial state: $ba1
  |After credit of $$5.0: $ba2
  |After debit of $$8.5: $ba3
  |After debit of $$10.0: $ba4""".stripMargin)
// -

// ### CLINQ

// +
// ** src/main/scala/progscala2/dynamic/clinq-example.sc
import progscala2.dynamic.CLINQ

def makeMap(name: String, capital: String, statehood: Int) =
  Map("name" -> name, "capital" -> capital, "statehood" -> statehood)

// "Records" for Five of the states in the U.S.A.
val states = CLINQ(
  List(
    makeMap("Alaska",     "Juneau",      1959),
    makeMap("California", "Sacramento",  1850),
    makeMap("Illinois",   "Springfield", 1818),
    makeMap("Virginia",   "Richmond",    1788),
    makeMap("Washington", "Olympia",     1889)))

// Field projections ("SELECT ..."):
states.name
states.capital
states.statehood
states.name_and_capital
states.name_and_statehood
states.capital_and_statehood
states.name_capital_and_statehood
states.all

// Add "WHERE" clauses.
states.all.where("name").NE("Alaska")
states.all.where("statehood").EQ(1889)
states.name_and_statehood.where("statehood").NE(1850)
// -

// ### XML

// +
// ** src/main/scala/progscala2/dsls/xml/writing.sc
import scala.xml._

val xml2 =
<sammich>
  <bread>wheat</bread>
  <meat>salami</meat>
  <condiments>
    <condiment expired="true">mayo</condiment>
    <condiment expired="false">mustard</condiment>
  </condiments>
</sammich>

XML.save("sammich.xml", xml2, "UTF-8")                               // <1>


// +
// ** src/main/scala/progscala2/dsls/xml/reading.sc
import scala.xml._                                                   // <1>

val xmlAsString = "<sammich>...</sammich>"                           // <2>
val xml1 = XML.loadString(xmlAsString)

val xml2 =                                                           // <3>
<sammich>
  <bread>wheat</bread>
  <meat>salami</meat>
  <condiments>
    <condiment expired="true">mayo</condiment>
    <condiment expired="false">mustard</condiment>
  </condiments>
</sammich>

for {                                                                // <4>
  condiment <- (xml2 \\ "condiment")
  if (condiment \ "@expired").text == "true"
} println(s"the ${condiment.text} has expired!")

def isExpired(condiment: Node): String =                             // <5>
  condiment.attribute("expired") match {
    case Some(Nil) | None => "unknown!"
    case Some(nodes) => nodes.head.text
  }

xml2 match {                                                         // <6>
  case <sammich>{ingredients @ _*}</sammich> => {
    for {
      condiments @ <condiments>{_*}</condiments> <- ingredients
      cond <- condiments \ "condiment"
    } println(s"  condiment: ${cond.text} is expired? ${isExpired(cond)}")
  }
}
// -

// ### Clicks

// +
// ** src/main/scala/progscala2/traits/ui2/click-count-observer.sc
import progscala2.traits.ui2._
import progscala2.traits.observer._

// No override of "click" in Button required.
val button = new Button("Click Me!") with ObservableClicks

class ClickCountObserver extends Observer[Clickable] {
  var count = 0
  def receiveUpdate(state: Clickable): Unit = count += 1
}

val bco1 = new ClickCountObserver
val bco2 = new ClickCountObserver

button addObserver bco1
button addObserver bco2

(1 to 5) foreach (_ => button.click())

assert(bco1.count == 5, s"bco1.count ${bco1.count} != 5")
assert(bco2.count == 5, s"bco2.count ${bco2.count} != 5")
println("Success!")

// +
// ** src/main/scala/progscala2/traits/ui2/vetoable-click-count-observer.sc
import progscala2.traits.ui2._
import progscala2.traits.observer._

// No override of "click" in Button required.
val button =
    new Button("Click Me!") with ObservableClicks with VetoableClicks {
  override val maxAllowed = 2                                   // <1>
}

class ClickCountObserver extends Observer[Clickable] {          // <2>
  var count = 0
  def receiveUpdate(state: Clickable): Unit = count += 1
}

val bco1 = new ClickCountObserver
val bco2 = new ClickCountObserver

button addObserver bco1
button addObserver bco2

(1 to 5) foreach (_ => button.click())

assert(bco1.count == 2, s"bco1.count ${bco1.count} != 2")       // <3>
assert(bco2.count == 2, s"bco2.count ${bco2.count} != 2")
println("Success!")

// +
// ** src/main/scala/progscala2/traits/ui/button-count-observer2.sc
import progscala2.traits.ui._
import progscala2.traits.observer._

val button = new Button("Click Me!") with Subject[Button] {

  override def click(): Unit = {
    super.click()
    notifyObservers(this)
  }
}

class ButtonCountObserver extends Observer[Button] {
  var count = 0
  def receiveUpdate(state: Button): Unit = count += 1
}

val bco1   = new ButtonCountObserver
val bco2   = new ButtonCountObserver

button addObserver bco1
button addObserver bco2

(1 to 5) foreach (_ => button.click())

assert(bco1.count == 5)
assert(bco2.count == 5)
println("Success!")

// +
// ** src/main/scala/progscala2/traits/ui/button-count-observer.sc
import progscala2.traits.ui._
import progscala2.traits.observer._

class ButtonCountObserver extends Observer[Button] {
  var count = 0
  def receiveUpdate(state: Button): Unit = count += 1
}

val button = new ObservableButton("Click Me!")
val bco1   = new ButtonCountObserver
val bco2   = new ButtonCountObserver

button addObserver bco1
button addObserver bco2

(1 to 5) foreach (_ => button.click())

assert(bco1.count == 5)
assert(bco2.count == 5)
// -

// ### Scripts

// +

#!/bin/sh
exec scala "$0" "$@"
!#

// @author weaves
//
// How to invoke scala as an executable script

import scala.io.Source

if (args.length > 0) {
  for (line <- Source.fromFile(args(0)).getLines())
    println(line.length +" "+ line)
}
else
  Console.err.println("Please enter filename")
// -

// # Unclassified

// ## Implementation Styles: Fibonacci

// +
// ** Imperative style

// 50! is negative for some reason
def fibImper(n: BigInt): BigInt = {
  var i = 0:BigInt
  var j = 1:BigInt

  for (k <- 0 until n.intValue) {
    val l:BigInt = i + j
    i = j
    j = l
  }
  i
}
// -

fibImper(50)

// +
// ** Recursive - stack-based, no tail recursion optimization.
// This will be slower and will stack overflow

def fibRec(n: Int): Int = {
  n match {
    case 0 => 0
    case 1 => 1
    case _ => fibRec(n-1) + fibRec(n-2)
  }
}
// -

// 40 is about the limit
fibRec(40)

// +
// ** Recursive - with tail recursion optimization.
// This needs to use BigInt because we pass b+c which can become to large for Int

def fibTailRec(n: BigInt): BigInt = {
  @annotation.tailrec 
  def f(a: BigInt, b: BigInt, c: BigInt): BigInt = if (a == 0) 0 else if(a < 2) c else f(a-1, c, b + c)

  f(n, 0, 1)
}
// -

// There is a weird error here, 50 becomes negative!
fibTailRec(50)

// +
// ** Recursive - stream implementation

val fibStream:Stream[Int] = 0 #:: 1 #:: (fib4 zip fibs.tail).map{ t => t._1 + t._2 }

lazy val fibs: Stream[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map { n => n._1 + n._2 }

// +
// But reduce can be used more innovatively with its magic _ parameters

def factorial(x: Int) = (2 to x).reduce(_ * _)

factorial(10)

// +
class Factorial2 {
  def factorial(n: Int): Int = {
    @tailrec 
    def factorialAcc(acc: Int, n: Int): Int = {
      if (n <= 1) acc
      else factorialAcc(n * acc, n - 1)
    }
    factorialAcc(1, n)
  }
  def apply(n: Int) = factorial(n)
}

// No companion object to avoid using new
val f2 = new Factorial2
f2.factorial(10)

// But with the apply we can do this
f2(10)

// +
import collection.mutable

//lazy
def fib0(n: Int): BigInt = {
  if(n <= 2) return 1
  var an1: BigInt = 1
  var an2: BigInt = 1
  var an: BigInt = 0
  for(i <- (3 to n)){
    var tmp: BigInt = 0
    an = an1 + an2
    tmp = an1
    an1 = an
    an2 = tmp
  }
  return an;
}

//non tail recursion
def fib1(n: Int): BigInt = if(n <= 2) 1 else fib1(n-1) + fib1(n-2)

//tail recursion
def fib2(n: Int, a:BigInt=1, b:BigInt=1): BigInt = {
    if(n <= 2) a else fib2(n - 1, a + b, a)
}

//fold version
def fib3(n: Int) = (1 to n).foldLeft(mutable.ListBuffer[BigInt](1, 1))((a, _) => (a(0) + a(1)) +=: a).reverse(n-1)
def fib4(n: Int) = (1 to n).foldLeft(mutable.ArrayBuffer[BigInt](1, 1))((a, _) => (a += a(a.size -1) + a(a.size -2)))(n-1)

//OO version
//non tail recursion
val fib5 = {
  val buf = mutable.ArrayBuffer[BigInt](1,1)
  def apply(n: Int):BigInt = n match{
    case n if n <= buf.size => buf(n-1)
    case n => {buf += (apply(n-2) + apply(n-1)); buf(n-1)}
  }
  apply _
}

//OO version
//memoization version
val fib6 = new Function1[Int, BigInt]{
  val buf = mutable.ArrayBuffer[BigInt](1,1)
  def apply(n: Int):BigInt = n match{
    case n if n <= buf.size => buf(n-1)
    case n => {buf += (apply(n-2) + apply(n-1)); buf(n-1)}
  }
}

// Stream version
def fib7(n: Int): BigInt = {
  def f(a: BigInt, b: BigInt): Stream[BigInt] = a #:: f(b, a + b)
    f(1, 1)(n-1)
}

// lazy val version 
// these don't work in REPL because a forward reference is needed.

// LazyList has replaced Stream in later Scala versions
// import scala.collection.immutable.LazyList

lazy val fib8: Stream[BigInt] = 0 #:: fib8.scanLeft(1:BigInt)(_ + _)
lazy val fib9: Stream[BigInt] = 0 #:: 1 #:: fib9.zip(fib9.tail).map{p=>p._1 + p._2}
// -

// ## Tips on Type

// +
// * @author weaves

// * Introductory examples

// +
// ** Type annotations and ascription

val x = 1

val x0:Byte = 0

val x1 = 0:Byte // preferred - 0 is set as a Byte, x1 takes that type.

// +
// *** Ascription

// This is essentially an up-cast.  

// +
// **** Upcast and downcast

// Upcasting means casting the object to a supertype, while
// downcasting means casting to a subtype. In java, upcasting is not
// necessary as it's done automatically. And it's usually referred as
// implicit casting. ... Downcasting is instead necessary because you
// defined a as object of Animal.

// +
// **** Narrowing and widening. 

// This was originally defined on on just primitive types. narrowing:
// converting a large (primitive) type to a smaller one - int to byte,
// for example.  widening is the opposite - byte to int - and doesn't
// lose information.

// It is now be used to refer to reference types and is similar to 
//
//  widening ~ upcast
//  narrowing ~ downcast

// A simple type ascription, we want to have an Object, not a String refer to the string
// s.

val s = "Dave"

val p = s: Object

// This then shows
p.getClass

// res3: Class[_ <: Object] = class java.lang.String
s.getClass
// 
// res4: Class[_ <: String] = class java.lang.String

// Similarly for these

Nil: List[String]

Set(values: _*)

"Daniel": AnyRef
// -

// ### Type Variance

// +
// ** Variance

// Class hierarchies allow the expression of subtype relationships. A
// central question that comes up when mixing OO with polymorphism is: if
// T’ is a subclass of T, is Container[T’] considered a subclass of
// Container[T]? Variance annotations allow you to express the following
// relationships between class hierarchies & polymorphic types:

//  	Meaning 	Scala notation
 
// covariant 	        C[T’] is a subclass of C[T] 	[+T]
// contravariant 	C[T] is a subclass of C[T’] 	[-T]
// invariant 	        C[T] and C[T’] are not related 	[T]

// Immutable collections can and should be covariant. Methods that receive the
// contained type should “downgrade” the collection appropriately.

// Mutable collections should not be covariant. 

// +
// ** Type Bounds

// https://twitter.github.io/scala_school/advanced-types.html#otherbounds

// For Scala 2.9 and before we had these

// A =:= B  A be equal to B
// A <:< B  A be a subtype of B
// A <%< B  A be viewable as B

// They have now been replaced =:= is still needed, but A<% B is the notation now.

// +
// ** Upper and lower type bounds

// In Scala, type parameters and abstract types may be constrained by a
// type bound. Such type bounds limit the concrete values of the type
// variables and possibly reveal more information about the members of
// such types. 

// +
// *** Upper type bound

// An upper type bound T <: A declares that type variable T
// must be derived from a type A, ie. T must be a sub-type of A.

// Here is an example that demonstrates
// upper type bound for a type parameter of class Cage:

abstract class Animal {
 def name: String
}

abstract class Pet extends Animal {}

class Cat extends Pet {
  override def name: String = "Cat"
}

class Dog extends Pet {
  override def name: String = "Dog"
}

class Lion extends Animal {
  override def name: String = "Lion"
}

// Introduce an upper type bound: P must be a sub-class of Pet.

class PetContainer[P <: Pet](p: P) {
  def pet: P = p
}
// -

val dogContainer = new PetContainer[Dog](new Dog)
val catContainer = new PetContainer[Cat](new Cat)

// +
val lionContainer = new PetContainer[Lion](new Lion)

// <console>:17: error: type arguments [Lion] do not conform to class PetContainer's type parameter bounds [P <: Pet]

// +
// *** Lower type bound

// While upper type bounds limit a type to a subtype of another type,
// lower type bounds declare a type to be a supertype of another
// type. 

// The term B >: A expresses that the type parameter B or the abstract
// type B refer to a supertype of type A. 
//
// In most cases, A will be the type parameter of the class and B will
// be the type parameter of a method.

// Here's a bad example of a partial implementation of a singly-linked list.

trait Node[+B] {
  def prepend(elem: B): Unit
}

// <console>:12: error: covariant type B occurs in contravariant position in type B of value elem
//          def prepend(elem: B): Unit

case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
  def prepend(elem: B) = ListNode[B](elem, this)
  def head: B = h
  def tail = t
}

case class Nil[+B]() extends Node[B] {
  def prepend(elem: B) = ListNode[B](elem, this)
}

// +
// However, this program does not compile because the parameter elem in
// prepend is of type B, which we declared covariant. This doesn’t work
// because functions are contravariant in their parameter types and
// covariant in their result types.

// Here is that example corrected

trait Node[+B] {
  def prepend[U >: B](elem: U)
}

case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
  def prepend[U >: B](elem: U) = ListNode[U](elem, this)
  def head: B = h
  def tail = t
}

case class Nil[+B]() extends Node[B] {
  def prepend[U >: B](elem: U) = ListNode[U](elem, this)
}

// Now, we can use this singly-linked list.

trait Mammal
case class AfricanSwallow() extends Mammal
case class EuropeanSwallow() extends Mammal


val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil())
val mammalList: Node[Mammal] = africanSwallowList

mammalList.prepend(new EuropeanSwallow)

// +
// ***** Note
// This isn't a very good example of a list. It doesn't actually prepend, but it does
// demonstrate that we can accept another sub-type of Mammal in a list that was originally 
// defined as another type of ListNode.

// +
// ** src/main/scala/effective/variance

trait Collection[+T] {
  def add[U >: T](other: U): Collection[U]
}

// Mutable collections should be invariant because it isn't possible to maintain
// referential integrity.

class HashSet[+T] {
  def add[U >: T](item: U) = {}
}

class Mammal
class Dog extends Mammal
class Cat extends Mammal

// If I now have a hash set of dogs

val dogs: HashSet[Dog]

// treat it as a set of Mammals and add a cat.

val mammals: HashSet[Mammal] = dogs
mammals.add(new Cat{})

// +
// *** Note
// This code doesn't run. It is only a partial implementation.

// +
// ** View Bounds

// https://twitter.github.io/scala_school/advanced-types.html#viewbounds

// Using implicit 

// Another feature of Scala the "Pimp my library" pattern to extend library implementations
// This is C++ friend notion with the implicit cast.

import scala.language.implicitConversions

class BlingString(string: String) {
    def bling = "*" + string + "*"
}

implicit def blingYoString(string: String) = new BlingString(string)

"Let's get blinged out!".bling

// +
// Relaxing a type constraint with a a View bound.

import scala.language.implicitConversions

implicit def strToInt(x: String) = x.toInt

// This sort of thing can be tested with
// implicitly defined as 
// def implicitly[T](implicit e: T): T = e


// There are a lot of subtleties here. 

// This doesn't bind to a type.
class Container[A] { def addIt[B <: A](x: B) = 123 + x }

// +
// Here we have a class that is defined by its template.
// We put a template on the method and that prevents the implicit.

// The implicit evidence does not 

// The class hierarchy for Scala *does not* relate Int as a sub-type of Long 
// It does allow conversion.

class Container[A] { def addIt[B](x: B)(implicit evidence: B <:< Int) = 123 + x }

(new Container[Int]).addIt(123)

(new Container[Int]).addIt(123:Short)

(new Container[Int]).addIt("123")	// this fails because of the evidence

// +
// Remove the type constraint on the method and use the evidence implicit.

class Container[A] { def addIt(x: A)(implicit evidence: A <:< Int) = 123 + x }

(new Container[Int]).addIt(123)

(new Container[Int]).addIt("123")	// this succeeds by applying the conversion

(new Container[BigInt]).addIt("123")	// this fails too

(new Container[Double]).addIt(123.0)	// this fails too.

// +
// Using direct inheritance Not at all useful with Scala, it has a
// View hierarchy and not a type hierarchy for Value types.

class Container[A <: Int] { def addIt(x: A) = 123 + x }

(new Container[Int]).addIt(123)

// But this uses the implicit conversion and doesn't need an evidence implicit.

(new Container[Int]).addIt("123")

// But still nothing for Float

(new Container[Int]).addIt(123.0)

// But does support Short, so there must be an implicit in operation.

(new Container[Int]).addIt(123:Short)

// +
// Use the view hierachy - still uses implicit conversions.

class Container[A <% Int] { def addIt(x: A) = 123 + x }

(new Container[Int]).addIt(123)

// And it uses the implicit conversion.

(new Container[Int]).addIt("123")

// But still nothing for Float

(new Container[Int]).addIt(123:Short)

// +
// Use the Numeric trait, but problems with + overloading if 123 is used.

class Container[A: Numeric] { def addIt[B <% A](x: B) = x }

(new Container[Double]).addIt(123:Short)

// case class doesn't work because B type is still generic and may not support toInt.

class Container[A: Numeric] {
  def addIt[B <% A](x: B) = x match {
    case Int => 123 + x.toInt
    case _ => x
  }
}

// +
// Overloads will work, but you must define the return type.

class Container[A: Numeric] {
  def addIt(x: Int) : Int = 123 + x
  def addIt(x: Double) : Int = 123 + x.toInt
  def addIt(x: Short) : Int = 123 + x.toInt
  def addIt(x: String) : Int = 123 + x
}

// But now, it doesn't use the implicit, it applies string concatenation.

(new Container[Double]).addIt(123:Short)
(new Container[Double]).addIt("123")

// +
// More overloads.

class Container[A: Numeric] {
  def addIt(x: Int) : Int = 123 + x
  def addIt(x: Double) : Int = 123 + x.toInt
  def addIt(x: Short) : Int = 123 + x.toInt
  // This won't work now, because the implicit gets in the way.
  // def addIt(x: String) : Int = 123 + x.toInt
  // So use it explicitly
  def addIt(x: String) : Int = 123 + strToInt(x)
}

(new Container[Double]).addIt(123:Short)
(new Container[Double]).addIt("123")

// But still stuck with the fixed return type.

// In List we see
// sum[B >: A](implicit num: Numeric[B]): B
// -

// ### Collection Methods

// +
// ** collection methods

val input = List(3, 5, 7, 11)

input.reduce((total, cur) => total + cur)

// More explicitly, initially total is set to head and cur the successor.

input.reduce((total, cur) => { println("t: " + total); println("c: " + cur); total+cur } )

// Define as a function and use infix

def op(total: Int, cur: Int) = total + cur

// Note the use of the 's' to define the string to "interpolate", as they say.
def op1(total: Int, cur: Int) = { println(s"t: $total; c: $cur"); total + cur }

input reduce op

input reduce op1

// This is in fact

op(op(op(3, 5), 7), 11)

// And we are adding up 

(((3 + 5) + 7) + 11)

// But this is simpler

input.sum 

// +
// But reduce can be used more innovatively with its magic _ parameters

def factorial(x: Int) = (2 to x).reduce(_ * _)

factorial(10)

// +
// The same can be achieved with foldLeft using an initial value 

input.foldLeft(0)(op)

(0 :: input).reduce(op)

// empty list is prepended by 0
(0 :: Nil).reduce(op)

// This is slightly different, the 0 is prepended.
input.foldLeft(0)(op1)

// There is a more operator based invocation of foldLeft
(0 /: input)(op)

// Introduce more operators

val reverse = (s: String) => s.reverse
val toUpper = (s: String) => s.toUpperCase
val appendBar = (s: String) => s + "bar"

// Coding imperatively

def applyTransformations(initial: String, transformations: Seq[String => String]) = {
 var cur = initial
 for(transformation <- transformations) {
  cur = transformation(cur)
 }
 cur
}
 
applyTransformations("foo", List(reverse, toUpper, appendBar))

applyTransformations("foo", List(appendBar, reverse, toUpper))

// And here we present a list of appendBar operators
applyTransformations("foo", List.fill(7)(appendBar))

// +
// Using tail-recursion
import scala.annotation.tailrec

@tailrec
def applyTransformations(initial: String, transformations: Seq[String => String]): String =
    transformations match {
        case head :: tail => applyTransformations(head(initial), tail)
        case Nil => initial
    }

// and then

applyTransformations("foo", List(appendBar, reverse, toUpper))

// +
class Factorial2 {
  def factorial(n: Int): Int = {
    @tailrec 
    def factorialAcc(acc: Int, n: Int): Int = {
      if (n <= 1) acc
      else factorialAcc(n * acc, n - 1)
    }
    factorialAcc(1, n)
  }
  def apply(n: Int) = factorial(n)
}

// No companion object to avoid using new
val f2 = new Factorial2
f2.factorial(10)

// But with the apply we can do this
f2(10)

// +
applyTransformations("foo", List(appendBar, reverse, toUpper))

// Signature of foldLeft
// def foldLeft[B](initial: B)(op: (B, A) => B): B

List(reverse, toUpper, appendBar).foldLeft("foo") {
    (cur, transformation) => transformation(cur)
}

// With some trace

def op2(cur: String, f: (String => String)) = { println(s"c: $cur"); f(cur) }

List(reverse, toUpper, appendBar).foldLeft("foo") {
    (cur, transformation) => op2(cur, transformation)
}

// With some trace: using an anonymous function.

List(reverse, toUpper, appendBar).foldLeft("foo") {
    (cur, transformation) => ( (x: String, y: (String => String) ) => { println(x); y(x) } )(cur, transformation)
}

( (x: Int, y: String) => (new StringBuilder()).append(x).append(" ").append(y) ) (1, "this")

// Using compose and initializing foldLeft with the identity function.

def composeAll[A](ts: Seq[A => A]): A => A = ts.foldLeft(identity[A] _)(_ compose _)
 
def applyTransformations(init: String, ts: Seq[String => String]): String = 
  composeAll(ts.reverse)(init)

applyTransformations("foo", List(reverse, toUpper, appendBar))

// Partial function as opposed to using Function1

// These are intended to support failure, unlike Function1. The concept of failure is
// that the function has no mapping for the given failure.

val sample = 1 to 10
val isEven: PartialFunction[Int, String] = {
  case x if x % 2 == 0 => x+" is even"
  // when it falls through here, then you it is has failed.
}

isEven isDefinedAt 3
isEven isDefinedAt 2

// the method collect can use isDefinedAt to select which members to collect
val evenNumbers = sample collect isEven

val isOdd: PartialFunction[Int, String] = {
  case x if x % 2 == 1 => x+" is odd"
}

// the method orElse allows chaining another partial function to handle
// input outside the declared domain
val numbers = sample map (isEven orElse isOdd)

// You can use lift() to make it a function that returns a Some

val isEvenV = isEven.lift

isEvenV(3)

isEvenV(2)

// Run with is a more efficient form of applyOrElse.

def f(s: String) = println("that's true")

isEven.runWith(f)(3)
isEven.runWith(f)(2)

// List work

List('a', 'b', 'c').aggregate(0)({ (sum, ch) => sum + ch.toInt }, { (p1, p2) => p1 + p2 })

val xs = Map("a" -> List(11,111), "b" -> List(22,222)).flatMap(_._2)

val ys = Map("a" -> List(1 -> 11,1 -> 111), "b" -> List(2 -> 22,2 -> 222)).flatMap(_._2)

List(1, 2, 3, 4).fold(0)( (x, y) => x+y)

// +
import scala.collection.mutable

type NumericList[T] = mutable.MutableList[Numeric[T]]

val l0 = new NumericList[Int]()

// Notice that a var declaration fails
// var l1 = new NumericList[Int]()

// +
// * Paradigmatic
// * Pimp my library

// This allows a library implementation to be extended using implicit methods.
// https://alvinalexander.com/scala/scala-2.10-implicit-class-example

// implicit cannot be used for top-level objects.

// var result = "HAL".increment

implicit class StringImprovements(s: String) {
  def increment = s.map(c => (c + 1).toChar)
}

val result = "HAL".increment

// +
// ** Using Option

// You may have a larger object, request, that has optional parameters

val name: Option[String] = /* request getParameter */ Option[String]("name")
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
println(upper getOrElse "")

// This can be expressed with a for comprehension

val upper = for {
  name <- /* request getParameter */ Option[String]("name")
  trimmed <- Some(name.trim)
  upper <- Some(trimmed.toUpperCase) if trimmed.length != 0
} yield upper
println(upper getOrElse "")

// Or using match.

val nameMaybe = /* request getParameter */ Option[String]("name")
nameMaybe match {
  case Some(name) =>
    println(name.trim.toUpperCase)
  case None =>
    println("No name value")
}

// +
// ** Polymorphism

// A useful property of Option is to be able to return None of a particular type.

Option.empty[Byte]

trait Container[M[_]] { def put[A](x: A): M[A]; def get[A](m: M[A]): A }

implicit val listContainer = new Container[List] { 
  def put[A](x: A) = List(x); def get[A](m: List[A]) = m.head 
}

implicit val optionContainer = new Container[Option] { 
  def put[A](x: A) = Option(x)
  def get[A](m: Option[A]) = m.get
}

def tupleize[M[_]: Container, A, B](fst: M[A], snd: M[B]) = {
  val c = implicitly[Container[M]]
  c.put(c.get(fst), c.get(snd))
}

// +
// *** Notes
// The Container trait supports put and get of a contained object.
// This means the type can be held in the container.
//
// The listContainer and optionContainer are effectively factory objects (singletons).
//
// The Container is needed because type inference will simplify structure types.

// This returns List[Short], forcing Byte to be a short and losing the type information.
List(1:Short, 1:Byte)

// The idea of the container is to hold the typed data unchanged.
{
  // Using List as a container we can preserve type info.
  val v1 = List(List(1:Short), List(1:Byte))
  println(v1(0)(0).getClass.getName + "; " + v1(1)(0).getClass.getName)
}

// tupleize removes a level of composition.
// and we can use Option 
tupleize(List(1), List(2))

tupleize(Option(1), Option(2))

{
  val v0 = tupleize(Option(1:Short), Option(2:Byte))
  println(v0.get._1.getClass.getName + "; " + v0.get._2.getClass.getName)

}

tupleize(List(1), List(2:Short))

tupleize(Option(1), Option(2:Short))

// To store an empty.

tupleize(Option(1:Short), Option(Option.empty[Byte]) )

tupleize(Option(1:Short), Option(None))

// And not - I have tried to adjust definitions.

tupleize(Option(1:Short), Option.empty[Byte] )

import scala.util.control.Exception._

def toByteOpt(s: String) = optionContainer put catching(classOf[NumberFormatException]) opt s.toByte
def toShortOpt(s: String) = optionContainer put catching(classOf[NumberFormatException]) opt s.toShort
def toIntOpt(s: String) = optionContainer put catching(classOf[NumberFormatException]) opt s.toInt

val l0 = List(toByteOpt _, toShortOpt _, toIntOpt _)

l0.map( _("1234") )

l0.find( !_("1234").isEmpty )


// But this could be used, but the type is inferred as Option[(Any, Any)]

{
  val x1 = tupleize(toByteOpt("1234") orElse Option(None), 
		    toByteOpt("123") orElse Option(None) )
  x1.get._2.getClass.getName
}

// * Typing strings

def toByteOpt(s: String) = catching(classOf[NumberFormatException]) opt s.toByte
def toShortOpt(s: String) = catching(classOf[NumberFormatException]) opt s.toShort
def toIntOpt(s: String) = catching(classOf[NumberFormatException]) opt s.toInt
def toLongOpt(s: String) = catching(classOf[NumberFormatException]) opt s.toLong
def toDoubleOpt(s: String) = catching(classOf[NumberFormatException]) opt s.toDouble
def toStringOpt(s: String) = Option[String](s)

val l0 = List(toByteOpt _, toShortOpt _, toIntOpt _, toLongOpt _, toDoubleOpt _, toStringOpt _)

val v0 = l0.map( _("1234")).find(!_.isEmpty).get.get.getClass.getName

val c0 = Class.forName(v0)

val cons = c0.getConstructor(Class.forName("java.lang.String"))

val s0 = cons.newInstance("1234").asInstanceOf[Short]

def instantiate[T](s: String)(implicit m: Manifest[T]): T = {
  val nm = m.erasure.getName
  val clazz = Class.forName(nm)
  val constructor = clazz.getConstructor(Class.forName("java.lang.String"))
  constructor.newInstance(s).asInstanceOf[T]
}

def instance(v0: String, s: String) = v0 match {
  case "java.lang.Short" => instantiate[java.lang.Short](s)
  case "java.lang.Integer" => instantiate[java.lang.Integer](s)
}

List(instantiate[java.lang.Short]("1234"), instantiate[java.lang.Integer]("1234") )

// +
// * @author weaves

// * Introductory examples

// +
// ** Wildcard Type

def f0(m:Map[_ <: AnyRef,_ <: AnyRef]):Int = {
  println(m.size)
  m.size
}

val m = sys.env

f0(m)

// +
// *** Note

// The <: is any sub-class of AnyRef.

// +
// ** Maps and iterations

m.foreach( x => println(s"$x._1 -> $x._2"))

for ((k,v) <- m) printf("key: %s, value: %s\n", k, v)

m foreach {case (key, value) => println (key + "-->" + value)}

// +
// *** Note

// The last operation is the case-class idiom. This was added to because foreach()
// (and others like map) would find the statement ambiguous.

// +
// ** Rich operations
// Some useful operations are available on the Rich Types.

0 max 5

0 min 5

// +
// *** Post-fix Operators

// Some operators are available as post-fix. But this need to be enabled with the
// following import.

import scala.language.postfixOps

-2.7 abs

-2.7 round

1.5 isInfinity

(1.0 / 0) isInfinity

4 to 6

"bob" capitalize

"robert" drop 2

// +
// *** Rich types

/*
 
scala.runtime.RichByte                Byte     
scala.runtime.RichShort		      Short  
scala.runtime.RichInt		      Int    
scala.runtime.RichChar		      Char   
scala.runtime.RichFloat		      Float  
scala.runtime.RichDouble	      Double 
scala.runtime.RichBoolean	      Boolean
scala.collection.immutable.StringOps  String 

http://www.scala-lang.org/api/2.11.8/index.html#scala.collection.immutable.StringOps

*/ 

// +
// *** Type Variance

/* 

Name 	
Description 	
Scala Syntax

Invariant
C[T'] and C[T] are not related
C[T]

Covariant
C[T'] is a subclass of C[T]
C[+T]

Contravariant
C[T] is a subclass of C[T']
C[-T]

Most immutable collections are covariant, and most mutable collections are invariant

This is one of the reasons, it better to use immutable collections where possible.

*/

// In this example, the derived types can be appended to a new collection of the base type.


class Fruit

case class Apple() extends Fruit

case class Orange() extends Fruit

val l1: List[Apple] = Apple() :: Nil

val l2: List[Fruit] = Orange() :: l1

// +
// **** Note

// The generic lists are covariant because of Fruit.

// +
// *** Generic Lists

// And also, it's safe to prepend with "anything",
// as we're building a new list - not modifying the previous instance

val l3: List[AnyRef] = "" :: l2

// Array is mutable so this won't compile, either though the target is "val"
val a: Array[Any] = Array[Int](1, 2, 3)

// But a wild card will allow it.
val a: Array[_ <: Any] = Array[Int](1, 2, 3)

// +
// ** Identifiers

// Identifiers paired back-ticks can make any token - even reserved words -
// identifiers.

val `yield`:Int = 1
println(`yield`)

val `no yield`:Int = 1
println(`no yield`)

// If it doesn't need backticks, but they're used
val `x0`:Int = 1
println(x0)

// +
// *** Note

// Spaces and other glyphs can be used.

// +
// ** Implicit conversions

// Introduce the Rational class. This has many features.
// In particular, there is linear construction. The require(), then the gcd(). 

// this() is an alternative constructor.

// various operator overloads.

class Rational(n: Int, d: Int) {
  require(d != 0)

  private val g = gcd(n.abs, d.abs)

  val numer = n / g
  val denom = d / g

  def this(n: Int) = this(n, 1)

  def + (that: Rational): Rational =
    new Rational(
      numer * that.denom + that.numer * denom,
      denom * that.denom
    )

  def + (i: Int): Rational =
    new Rational(numer + i * denom, denom)

  def - (that: Rational): Rational =
    new Rational(
      numer * that.denom - that.numer * denom,
      denom * that.denom
    )

  def - (i: Int): Rational =
    new Rational(numer - i * denom, denom)

  def * (that: Rational): Rational =
    new Rational(numer * that.numer, denom * that.denom)

  def * (i: Int): Rational =
    new Rational(numer * i, denom)

  def / (that: Rational): Rational =
    new Rational(numer * that.denom, denom * that.numer)

  def / (i: Int): Rational =
    new Rational(numer, denom * i)

  // Note that override has to be stated.
  override def toString = numer +"/"+ denom

  private def gcd(a: Int, b: Int): Int =
    if (b == 0) a else gcd(b, a % b)
}

val r = new Rational(66, 47)

r * 2

// but not

2 * r

/// *** Add an implicit method

// so introduce an implicit using this package to notify the interpreter.

import scala.language.implicitConversions

implicit def intToRational(x: Int) = new Rational(x)

2 * r

/// **** Note: now works.

// +
// ** Control structures and iteration

// +
// *** listFiles is a Java method.

val filesHere = (new java.io.File(".")).listFiles

for (file <- filesHere)
  println(file)

// +
// **** Note: java.io.File.listfiles

// This can be called without (). It returns an array.

// +
// *** Using yield

// yield is not similar to Python's generator. It re-scopes the file variable and
// takes it as a return value.

def scalaFiles =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala")
  } yield file

// +
// *** try-catch

// Render an Array as a List
// And typing a var for a block.

import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException

val names = scalaFiles.toList.head :: List(new java.io.File("input.txt"))

var f:java.io.FileReader = null

for (fn <- names) 
  try {
    println("file: " + fn)
    f = new FileReader(fn)
    // Use and close file
  } catch {
    case ex: FileNotFoundException => println("no file: " + fn.toString)// Handle missing file
    case ex: IOException => println("io" + fn.toString) // IO error
  } finally {
    f.close
  }

// +
// **** Note

// Other run-time environments support Automatic Resource Management methods.

// Better solutions are .Net using () {}
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement

// Scala has some support, but not in the language yet?
// Scala Automatic Resource Management
// https://github.com/jsuereth/scala-arm

// +
// *** Implementing AutoCloseable

import java.lang.AutoCloseable
import java.nio.file.{Files, Paths}
import java.util.Optional

def autoClose[A <: AutoCloseable,B](closeable: A)(fun: (A) ⇒ B): B = {
    try {
        fun(closeable)
    } finally {
        closeable.close()
    }
}

val result: Optional[String] = autoClose(Files.lines(Paths.get(fn.toString))) { stream ⇒
    stream.findAny()
}

// +
// ** Companion Objects

// Note: List is abstract so this cannot use the "new" keyword.
// List has a companion object that acts as a factory.

// Like this one

class Person {
    var name: String = _
  var age: Int = 0
}

object Person {
    def apply(name: String): Person = {
        var p = new Person
        p.name = name
        p
    }
    def apply(name: String, age: Int): Person = {
        var p = new Person
        p.name = name
        p.age = age
        p
    }
}

// +
// *** Note: overloads of apply

// you can have overload the apply method

// +
// *** Examples

val pete = Person("Pete")
val peter = Person("Peter", 42)

case class Person1 (var name: String)

val dave = Person1("Dave")

pete.name
peter.name
peter.age

dave.name

// +
// **** Note

// You can see the alternative constructions.

// +
// ** Annotations (or Decorations) 
// Scala supports these

// http://www.scala-lang.org/api/2.12.0/scala/annotation/Annotation.html

// +
// *** Important
// tailrec 
// implicitNotFound implicitAmbiguous

// +
// ** Switch statement
// This is replaced by match

val args = List[Any]("salt", 1)

val firstArg = if (args.length > 0) args(0) else ""
firstArg match {
  case "salt" => println("pepper")
  case "chips" => println("salsa")
  case "eggs" => println("bacon")
  case _ => println("huh?")
}

for(arg <- args) {
  arg match {
    case "salt" => println("pepper")
    case "chips" => println("salsa")
    case "eggs" => println("bacon")
    case 1 => println("1:Int")
    case _ => println("huh?")
  }
}

// +
// *** Note
// This is very flexible, it can support mixed types too.

// +
// ** Loop control: break and continue

// Scala does not directly support break and continue, but there is a breakable block extension.

import scala.util.control.Breaks._
import java.io._
val in = new BufferedReader(new InputStreamReader(System.in))
breakable {
  while (true) {
    println("? ")
    if (in.readLine() == "") break
  }
}

// +
// *** Note
// This is a feature of Scala that isn't too easy to appreciate. Scala only has
// expressions. 

// +
// ** Variable scope

// Almost like Java, but does allow new variable with a name from an outer scope.

// +
// ** Functions and closures

// A function is a Lambda and is an object.

var increase = (x: Int) => x + 9999

// +
// *** Some currying 

def sum(a: Int, b: Int, c: Int) = a + b + c

val sum1 = sum(1, _:Int, 3)

6 == sum1(2)

// +
// *** Partially applied function
// Note the trailing underscore

val sum2 = sum _

// +
// *** Closure

// By contrast, Java’s inner classes do not allow you to access
// modifiable variables in surrounding scopes at all, so there is no
// difference between capturing a variable and capturing its currently
// held value.

// This captures the value of 'more' and uses it within a lambda function.

def makeIncreaser(more: Int) = (x: Int) => x + more

val add10 = makeIncreaser(10)

val add15 = makeIncreaser(15)

add10(5)
add10(15)

// +
// ** Function Parameters Definition 

// +
// *** Repeated parameters use the star *

def echo(args: String*) = for (arg <- args) println(arg)

echo("a", "b")

// +
// *** Arrays can be expanded when passed

val arr = Array("a", "b", "c")

echo(arr: _*)

// +
// *** Named arguments

def speed(distance: Float, time: Float): Float =
  distance / time

/// *** Note: and by name

speed(time = 10, distance = 120)

// +
// *** Default value for a parameter

def printTime(out: java.io.PrintStream = System.out, divisor: Int = 1) =
  out.println("time = "+ System.currentTimeMillis()/divisor)

// +
// *** Empty brackets required
// The following doesn't work, (it returns the partially applied function)
// The brackets are required.

printTime

printTime()

printTime(out = System.err, divisor = 1000000)

// +
// ** Tail Recursion

// It is possible to implement many methods without recursion because it may appear
// to be less expensive - less stack frames. But with tail-recursion, the Scala
// compiler can apply an optimization and use only one stack frame. (It does the
// iteration for you. Type 0 (ForTran) and Type 1 (C-based runtime) languages.)

// But the tail-recursion must be well-formed - the recursion must be the last
// invocation in the method.

// +
// *** Tracing: bad: many stack frames

// This isn't tail-recursive because of the + 1 at the end. 

def boom(x: Int): Int = { 
  if (x == 0) throw new Exception("boom!") 
  else boom(x - 1) + 1
}

boom(3)

// +
// **** Note
// The exception shows 4 stack frames, one for the initial invocation and then 3 for
// the recursive ones - counting down to zero.

// +
// *** Tracing: good: one stack frame

def bang(x: Int): Int = {
  if (x == 0) throw new Exception("bang!")
  else bang(x - 1)
}

bang(3)

// The exception now shows 1 stack frame

// +
// *** Tracing: good: one stack frame with annotation

// The annotation helps the compiler apply the stack frame optimization.

@annotation.tailrec def bang(x: Int): Int = {
  if (x == 0) throw new Exception("bang!")
  else bang(x - 1)
}

bang(3)

// +
// *** Tracing: bad: annotation throws compile-error

@annotation.tailrec def boom(x: Int): Int = { 
  if (x == 0) throw new Exception("boom!") 
  else boom(x - 1)
}

boom(3)



StarcoderdataJupyterScriptsDedupFiltered