Date post: | 10-May-2015 |
Category: |
Technology |
Upload: | dean-wampler |
View: | 4,726 times |
Download: | 1 times |
The Seductions of Scala
1
Dean [email protected]@deanwamplerpolyglotprogramming.com/talks
April 3, 2011
Co-author,Programming
Scala
programmingscala.com
2
<shameless-plug/>
Why do we need a new language?
3
#1We needFunctional
Programming…
4
… for concurrency.… for concise code.… for correctness.
5
#2We need a better
Object Model…
6
… for composability.… for scalable designs.
7
Scala’s Thesis: Functional Prog.
Complements Object-Oriented
Prog.
8
Scala’s Thesis: Functional Prog.
Complements Object-Oriented
Prog.Despite surface contradictions...
8
But we want tokeep our investment
in Java/C#.
9
Scala is...
• A JVM and .NET language.
• Functional and object oriented.
• Statically typed.
• An improved Java/C#.
10
Martin Odersky
• Helped design java generics.
• Co-wrote GJ that became javac (v1.3+).
• Understands Computer Science and Industry.
11
Everything can be a Function
12
Objects as
Functions13
14
class Logger(val level:Level) {
def apply(message: String) = { // pass to Log4J... Log4J.log(level, message) }}
class Logger(val level:Level) {
def apply(message: String) = { // pass to Log4J... Log4J.log(level, message) }}
15
class Logger(val level:Level) {
def apply(message: String) = { // pass to Log4J... Log4J.log(level, message) }}
15
class body is the “primary” constructor
class Logger(val level:Level) {
def apply(message: String) = { // pass to Log4J... Log4J.log(level, message) }}
15
makes “level” a field
class body is the “primary” constructor
class Logger(val level:Level) {
def apply(message: String) = { // pass to Log4J... Log4J.log(level, message) }}
15
makes “level” a field
class body is the “primary” constructor
method
16
class Logger(val level:Level) {
def apply(message: String) = { // pass to Log4J... Log4J.log(level, message) }}
val error = new Logger(ERROR)
16
class Logger(val level:Level) {
def apply(message: String) = { // pass to Log4J... Log4J.log(level, message) }}
val error = new Logger(ERROR)
16
class Logger(val level:Level) {
def apply(message: String) = { // pass to Log4J... Log4J.log(level, message) }}
…error("Network error.")
17
class Logger(val level:Level) {
def apply(message: String) = { // pass to Log4J... Log4J.log(level, message) }}
…error("Network error.")
17
class Logger(val level:Level) {
def apply(message: String) = { // pass to Log4J... Log4J.log(level, message) }}
…error("Network error.")
“function object”apply is called
When you put an arg list
after any object,apply is called.
18
Everything is an Object
19
Int, Double, etc. are true objects.
20
Int, Double, etc. are true objects.
20
But they are compiled to primitives.
Functions as
Objects21
First, About Lists
22
val list = List(1, 2, 3, 4, 5)
First, About Lists
The same as this “list literal” syntax:
22
val list = List(1, 2, 3, 4, 5)
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
23
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
23
“cons”
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
empty list
23
“cons”
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
empty list
23
“cons”
head
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
empty list
23
“cons”
tailhead
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
24
Baked into the Grammar?
val list = Nil.::(5).::(4).::( 3).::(2).::(1)
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
No, just method calls!
24
Baked into the Grammar?
val list = Nil.::(5).::(4).::( 3).::(2).::(1)
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
25
val list = Nil.::(5).::(4).::( 3).::(2).::(1)
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
25
Method names can contain almost any character.
val list = Nil.::(5).::(4).::( 3).::(2).::(1)
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
26
Any method ending in “:” binds to the right!
val list = Nil.::(5).::(4).::( 3).::(2).::(1)
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
27
If a method takes one argument, you can drop the “.” and the parentheses, “(“ and “)”.
"hello" + "world"
28
Infix Operator Notation
"hello" + "world"
is actually just
28
Infix Operator Notation
"hello".+("world")
Similar mini-DSLs have been defined for other types.
29
Similar mini-DSLs have been defined for other types.
29
Also in many third-party libraries.
Back tofunctions as objects...
30
Classic Operations on “Container” Types
31
List, Map, ... map
fold/reduce
filter
32
val list = "a" :: "b" :: Nil
32
list map { s => s.toUpperCase }
// => "A" :: "B" :: Nil
val list = "a" :: "b" :: Nil
33
list map { s => s.toUpperCase }
33
list map { s => s.toUpperCase }
map called on list (dropping the “.”)
33
list map { s => s.toUpperCase }
map called on list (dropping the “.”) argument to map
(using “{“ vs. “(“)
33
list map { s => s.toUpperCase }
map called on list (dropping the “.”) argument to map
(using “{“ vs. “(“)
“function literal”
33
list map { s => s.toUpperCase }
map called on list (dropping the “.”)
functionargument list
argument to map (using “{“ vs. “(“)
“function literal”
33
list map { s => s.toUpperCase }
map called on list (dropping the “.”)
functionargument list
function body
argument to map (using “{“ vs. “(“)
“function literal”
34
list map { s => s.toUpperCase }
34
list map { s => s.toUpperCase }
inferred type
34
list map { s => s.toUpperCase }
list map { (s:String) => s.toUpperCase }
Explicit type
inferred type
So far, we have usedtype inference
a lot...
35
How the Sausage Is Made
class List[A] { … def map[B](f: A => B): List[B] …}
36
How the Sausage Is Made
class List[A] { … def map[B](f: A => B): List[B] …}
36
Parameterized type
How the Sausage Is Made
class List[A] { … def map[B](f: A => B): List[B] …}
36
Declaration of map
The function argument
Parameterized type
map’s return type
How the Sausage Is Made
trait Function1[-A,+R] {
def apply(a:A): R …}
37
How the Sausage Is Made
trait Function1[-A,+R] {
def apply(a:A): R …}
37
like an “abstract” class
How the Sausage Is Made
trait Function1[-A,+R] {
def apply(a:A): R …}
37
No method body:=> abstract
like an “abstract” class
How the Sausage Is Made
trait Function1[-A,+R] {
def apply(a:A): R …}
37
No method body:=> abstract
like an “abstract” class“contravariant”,
“covariant” typing
(s:String) => s.toUpperCase
38
new Function1[String,String] { def apply(s:String) = { s.toUpperCase }}
What the Compiler Does
(s:String) => s.toUpperCase
38
What you write.
new Function1[String,String] { def apply(s:String) = { s.toUpperCase }}
What the compiler generates
An anonymous class
What the Compiler Does
(s:String) => s.toUpperCase
38
What you write.
new Function1[String,String] { def apply(s:String) = { s.toUpperCase }}
What the compiler generates
An anonymous class
What the Compiler Does
No “return” needed
Recap
39
list map { s => s.toUpperCase }
// => "A" :: "B" :: Nil
val list = "a" :: "b" :: Nil
Recap
39
list map { s => s.toUpperCase }
// => "A" :: "B" :: Nil
val list = "a" :: "b" :: Nil
Function “object”
Recap
39
list map { s => s.toUpperCase }
// => "A" :: "B" :: Nil
val list = "a" :: "b" :: Nil
Function “object”
{…} ok, instead of (…)
More Functional
Hotness40
sealed abstract class Option[+T] {…}
case class Some[+T](value: T) extends Option[T] {…}
case object None extends Option[Nothing] {…}
Avoiding Nulls
41
sealed abstract class Option[+T] {…}
case class Some[+T](value: T) extends Option[T] {…}
case object None extends Option[Nothing] {…}
Avoiding Nulls
41An Algebraic Data Type
// Java style (schematic) class Map[K, V] { def get(key: K): V = { return value || null; }}
42
// Java style (schematic) class Map[K, V] { def get(key: K): V = { return value || null; }}
42
// Java style (schematic) class Map[K, V] { def get(key: K): V = { return value || null; }}
42Which is the better API?
// Scala styleclass Map[K, V] { def get(key: K): Option[V] = { return Some(value) || None; }}
val m = Map("one" -> 1, "two" -> 2)…val n = m.get("four") match { case Some(i) => i case None => 0 // default}
43
In Use:
val m = Map("one" -> 1, "two" -> 2)…val n = m.get("four") match { case Some(i) => i case None => 0 // default}
43
Use pattern matching to extract the value (or not)
In Use:Literal syntax for map creation
sealed abstract class Option[+T] {…}
Option Details: sealed
44
sealed abstract class Option[+T] {…}
Option Details: sealed
44
All children must be defined in the same file
case class Some[+T](value: T)
Case Classes
45
case class Some[+T](value: T)
Case Classes
45
●Provides factory method, pattern matching, equals, toString, etc.●Makes “value” a field without val keyword.
Object
46
case object None extends Option[Nothing] {…}
Object
46
A “singleton”. Only one instance will exist.
case object None extends Option[Nothing] {…}
case object None extends Option[Nothing] {…}
Nothing
47
case object None extends Option[Nothing] {…}
Nothing
47
Special child type of all other types. Used for this special
case where no actual instances required.
Succinct Code 48
Succinct Code 48
A few things we’ve seen so far.
"hello" + "world"
Infix Operator Notation
49
same as
"hello" + "world"
"hello".+("world")
Infix Operator Notation
49
Type Inference
50
// JavaHashMap<String,Person> persons = new HashMap<String,Person>();
Type Inference
50
// JavaHashMap<String,Person> persons = new HashMap<String,Person>();
vs.
// Scalaval persons = new HashMap[String,Person]
Type Inference
51
// JavaHashMap<String,Person> persons = new HashMap<String,Person>();
vs.
// Scalaval persons = new HashMap[String,Person]
Type Inference
51
// JavaHashMap<String,Person> persons = new HashMap<String,Person>();
vs.
// Scalaval persons = new HashMap[String,Person]
52
// Scalaval persons = new HashMap[String,Person]
52
// Scalaval persons = new HashMap[String,Person]
no () needed.Semicolons inferred.
User-defined Factory Methods
53
val words = List("Scala", "is", "fun!")
User-defined Factory Methods
53
val words = List("Scala", "is", "fun!")
no new needed.Can return a subtype!
class Person { private String firstName; private String lastName; private int age;
public Person(String firstName, String lastName, int age){ this.firstName = firstName; this.lastName = lastName; this.age = age; } public void String getFirstName() {return this.firstName;} public void setFirstName(String firstName) { this.firstName = firstName; }
public void String getLastName() {return this.lastName;} public void setLastName(String lastName) { this.lastName = lastName; }
public void int getAge() {return this.age;} public void setAge(int age) { this.age = age; } } 54
class Person { private String firstName; private String lastName; private int age;
public Person(String firstName, String lastName, int age){ this.firstName = firstName; this.lastName = lastName; this.age = age; } public void String getFirstName() {return this.firstName;} public void setFirstName(String firstName) { this.firstName = firstName; }
public void String getLastName() {return this.lastName;} public void setLastName(String lastName) { this.lastName = lastName; }
public void int getAge() {return this.age;} public void setAge(int age) { this.age = age; } }
Typical Java54
class Person( var firstName: String, var lastName: String, var age: Int)
55
class Person( var firstName: String, var lastName: String, var age: Int)
55
Typical Scala!
class Person( var firstName: String, var lastName: String, var age: Int)
56
class Person( var firstName: String, var lastName: String, var age: Int)
Class body is the“primary” constructor
Parameter list for c’tor
Makes the arg a fieldwith accessors
No class body {…}. nothing else needed!
56
case class Person( firstName: String, lastName: String, age: Int)
57
Even Better...
case class Person( firstName: String, lastName: String, age: Int)
57
Constructor args are automatically vals, plus other goodies.
Even Better...
Scala’s Object Model: Traits
58
Scala’s Object Model: Traits
58
Composable Units of Behavior
We would like to compose objects
from mixins.
59
Java: What to Do?class Server extends Logger { … }
60
Java: What to Do?class Server extends Logger { … }
60
“Server is a Logger”?
Java: What to Do?class Server extends Logger { … }
60
class Server implements Logger { … }
“Server is a Logger”?
Logger isn’t an interface!
Java’s object model
61
Java’s object model
• Good
• Promotes abstractions.
• Bad
• No composition through reusable mixins.
61
Like interfaces with implementations,
62
Traits
… or like abstract classes +
multiple inheritance (if you prefer).
63
Traits
64
trait Logger { val level: Level // abstract
def log(message: String) = { Log4J.log(level, message) }}
Logger as a Mixin:
64
trait Logger { val level: Level // abstract
def log(message: String) = { Log4J.log(level, message) }}
Logger as a Mixin:
Traits don’t have constructors, but
you can still define fields.
65
trait Logger { val level: Level // abstract …}
Logger as a Mixin:
val server = new Server(…) with Logger { val level = ERROR }server.log("Internet down!!")
65
trait Logger { val level: Level // abstract …}
Logger as a Mixin:
val server = new Server(…) with Logger { val level = ERROR }server.log("Internet down!!")
65
trait Logger { val level: Level // abstract …}
Logger as a Mixin:
mixed in Logging
val server = new Server(…) with Logger { val level = ERROR }server.log("Internet down!!")
65
trait Logger { val level: Level // abstract …}
Logger as a Mixin:
mixed in Logging
abstract member defined
Actor Concurrency
66
When you share mutable
state...
67
When you share mutable
state...
Hic sunt dracones(Here be dragons)
67
Actor Model
• Message passing between autonomous actors.
• No shared (mutable) state.
68
Actor Model
• First developed in the 70’s by Hewitt, Agha, Hoare, etc.
• Made “famous” by Erlang.
• Scala’s Actors patterned after Erlang’s.
69
“self” Display
draw
draw
??? error!
exit“exit”
2 Actors:
70
package shapes
case class Point( x: Double, y: Double) abstract class Shape { def draw() }
Hierarchy of geometric shapes71
package shapes
case class Point( x: Double, y: Double) abstract class Shape { def draw() }
abstract “draw” method
Hierarchy of geometric shapes71
case class Circle( center:Point, radius:Double) extends Shape { def draw() = …}
case class Rectangle( ll:Point, h:Double, w:Double) extends Shape { def draw() = …}
72
case class Circle( center:Point, radius:Double) extends Shape { def draw() = …}
case class Rectangle( ll:Point, h:Double, w:Double) extends Shape { def draw() = …}
concrete “draw” methods
72
package shapes import akka.actor._ class ShapeDrawingActor extends Actor { def receive = { … }}
73
Actor for drawing shapes
package shapes import akka.actor._ class ShapeDrawingActor extends Actor { def receive = { … }}
73
Use the “Akka” Actor library
Actor for drawing shapes
package shapes import akka.actor._ class ShapeDrawingActor extends Actor { def receive = { … }}
73
Use the “Akka” Actor library
Actor
receive and handle each message
Actor for drawing shapes
receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x)}
74
Receive method
Actor for drawing shapes
receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x)}
75
Actor for drawing shapes
receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x)}
75
pattern matching
Actor for drawing shapes
receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x)}
75
pattern matching
Actor for drawing shapes
receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x)}
75
pattern matching
Actor for drawing shapes
receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x)}
76
receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x)}
draw shape & send reply
76
receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x)}
done
76
receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x)}
unrecognized message76
receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x)}
76
77
package shapes import akka.actor._class ShapeDrawingActor extends Actor { receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x) }}
Altogether77
package shapes import akka.actor._class ShapeDrawingActor extends Actor { receive = { case s:Shape => print("-> "); s.draw() self.reply("Shape drawn.") case "exit" => println("-> exiting...") self.reply("good bye!") case x => // default println("-> Error: " + x) self.reply("Unknown: " + x) }}
import shapes._import akka.actor._import akka.actor.Actor
object Driver { def main(args:Array[String])={ val driver = actorOf[Driver] driver.start driver ! "go!" }}class Driver …
78
import shapes._import akka.actor._import akka.actor.Actor
object Driver { def main(args:Array[String])={ val driver = actorOf[Driver] driver.start driver ! "go!" }}class Driver … driver to try it out
78
a “singleton” type to hold main
import shapes._import akka.actor._import akka.actor.Actor
object Driver { def main(args:Array[String])={ val driver = actorOf[Driver] driver.start driver ! "go!" }}class Driver … driver to try it out
78
a “singleton” type to hold main
import shapes._import akka.actor._import akka.actor.Actor
object Driver { def main(args:Array[String])={ val driver = actorOf[Driver] driver.start driver ! "go!" }}class Driver … driver to try it out
78
a “singleton” type to hold main
! is the message send “operator”
…class Driver extends Actor { val drawer = actorOf[ShapeDrawingActor] drawer.start def receive = { … }}
driver to try it out79
…class Driver extends Actor { val drawer = actorOf[ShapeDrawingActor] drawer.start def receive = { … }}
driver to try it out79
Its “companion” object Driver was on the previous slide.
def receive = { case "go!" => drawer ! Circle(Point(…),…) drawer ! Rectangle(…) drawer ! 3.14159 drawer ! "exit" case "good bye!" => println("<- cleaning up…") drawer.stop; self.stop case other => println("<- " + other)}
80
driver to try it out
def receive = { case "go!" => drawer ! Circle(Point(…),…) drawer ! Rectangle(…) drawer ! 3.14159 drawer ! "exit" case "good bye!" => println("<- cleaning up…") drawer.stop; self.stop case other => println("<- " + other)}
80
driver to try it out
sent by main
def receive = { case "go!" => drawer ! Circle(Point(…),…) drawer ! Rectangle(…) drawer ! 3.14159 drawer ! "exit" case "good bye!" => println("<- cleaning up…") drawer.stop; self.stop case other => println("<- " + other)}
80
driver to try it out
sent by main
sent bydrawer
// run Driver.main (see github README.md)-> drawing: Circle(Point(0.0,0.0),1.0)-> drawing: Rectangle(Point(0.0,0.0),2.0,5.0)-> Error: 3.14159-> exiting...<- Shape drawn.<- Shape drawn.<- Unknown: 3.14159<- cleaning up...
81
“<-” and “->” messages may be interleaved!!
case "go!" => drawer ! Circle(Point(…),…) drawer ! Rectangle(…) drawer ! 3.14159 drawer ! "exit"
… // ShapeDrawingActorreceive = { case s:Shape => s.draw() self.reply("…")
case … case … }
82
… // ShapeDrawingActorreceive = { case s:Shape => s.draw() self.reply("…")
case … case … }
Functional-style pattern matching
82
… // ShapeDrawingActorreceive = { case s:Shape => s.draw() self.reply("…")
case … case … }
Functional-style pattern matching
“Switch” statements are not evil!
Object-oriented-style polymorphism
82
Recap
83
Scala is...
84
85
a better Java and C#,
85
86
object-orientedand
functional,86
87
succinct, elegant,
andpowerful.
87
@deanwampler
polyglotprogramming.com/talksprogrammingscala.com
thinkbiganalytics.com
88
Extra Slides 89
Modifying Existing Behaviorwith Traits
90
Example
trait Queue[T] { def get(): T def put(t: T) }
91
Example
trait Queue[T] { def get(): T def put(t: T) }
A pure abstraction (in this case...)91
Log put trait QueueLogging[T] extends Queue[T] { abstract override def put( t: T) = { println("put("+t+")") super.put(t) }}
92
Log put trait QueueLogging[T] extends Queue[T] { abstract override def put( t: T) = { println("put("+t+")") super.put(t) }}
93
Log put trait QueueLogging[T] extends Queue[T] { abstract override def put( t: T) = { println("put("+t+")") super.put(t) }}
What is “super” bound to??
93
class StandardQueue[T] extends Queue[T] { import ...ArrayBuffer private val ab = new ArrayBuffer[T] def put(t: T) = ab += t def get() = ab.remove(0) … }
94
class StandardQueue[T] extends Queue[T] { import ...ArrayBuffer private val ab = new ArrayBuffer[T] def put(t: T) = ab += t def get() = ab.remove(0) … }
Concrete (boring) implementation94
val sq = new StandardQueue[Int] with QueueLogging[Int] sq.put(10) // #1 println(sq.get()) // #2 // => put(10) (on #1) // => 10 (on #2)
95
val sq = new StandardQueue[Int] with QueueLogging[Int] sq.put(10) // #1 println(sq.get()) // #2 // => put(10) (on #1) // => 10 (on #2)
Example use95
val sq = new StandardQueue[Int] with QueueLogging[Int] sq.put(10) // #1 println(sq.get()) // #2 // => put(10) (on #1) // => 10 (on #2)
96
val sq = new StandardQueue[Int] with QueueLogging[Int] sq.put(10) // #1 println(sq.get()) // #2 // => put(10) (on #1) // => 10 (on #2)
Mixin composition; no class required
96
Example use
Traits give us advice, but not a join point “query” language.
97
Like Aspect-Oriented Programming?
Traits are a powerful composition mechanism!
98
Functional Programming
99
What is Functional
Programming?
100
What is Functional
Programming?Don’t we already write “functions”?
100
y = sin(x)
101
y = sin(x)
101
Based on Mathematics
y = sin(x)
102
y = sin(x)
Setting x fixes y
∴ variables are immutable102
20 += 1 ??
103
20 += 1 ??We never modify the 20 “object”
103
Concurrency
104
No mutable state
∴ nothing to synchronize
Concurrency
104
When you share mutable
state...
105
When you share mutable
state...
Hic sunt dracones(Here be dragons)
105
y = sin(x)
106
y = sin(x)Functions don’t
change state∴ side-effect free
106
Side-effect free functions
• Easy to reason about behavior.
• Easy to invoke concurrently.
• Easy to invoke anywhere.
• Encourage immutable objects.
107
tan(Θ) = sin(Θ)/cos(Θ)
108
tan(Θ) = sin(Θ)/cos(Θ)
Compose functions of other functions
∴ first-class citizens108
Even More Functional
Hotness109
val l = List( Some("a"), None, Some("b"), None, Some("c"))
for (Some(s) <- l) yield s// List(a, b, c)
110
For “Comprehensions”
val l = List( Some("a"), None, Some("b"), None, Some("c"))
for (Some(s) <- l) yield s// List(a, b, c)
110
No “if ” statement
Pattern match; only take elements of “l”
that are Somes.
For “Comprehensions”