Seductions of Scala

Preview:

DESCRIPTION

A 90-minute introduction to the Scala programming language and why it's a seductive choice for modern software development.

Citation preview

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

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”

Recommended