Skip to Content
Course content

7: Functions and Methods

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first start with Scala, you'll probably use the def keyword for everything. It feels natural—you're defining a piece of logic, you give it a name, and you call it. For a long time, I did the same thing, treating Scala like a slightly fancier version of Java. I assumed that a "function" and a "method" were just two words for the same thing. But in Scala, they are fundamentally different beasts, and confusing them is a one-way ticket to some very strange compiler errors when you start getting into higher-order programming.

The illusion that everything is just a method

Let's look at a common scenario: calculating discounts for an e-commerce store. A naive approach is to define every discount rule as a method within a service class. It looks clean at first glance:

class DiscountService {
  def seasonalDiscount(price: Double): Double = price * 0.9
  def vipDiscount(price: Double): Double = price * 0.8
  def noDiscount(price: Double): Double = price
}

This works fine if you're just calling service.vipDiscount(100.0). But here is where the "method-only" mindset breaks. Imagine you want to write a function that applies a discount to a whole list of orders. You might try to pass one of those methods as an argument to another method. In many languages, this is a breeze. In Scala, a def is not a value; it's a piece of the class definition. You can't just "hand it over" to another function because it doesn't exist as an object in memory—it's just a set of instructions tied to the instance of DiscountService.

Treating logic as data

To solve this, we need to move from methods to actual functions. In Scala, a function is a first-class object. You can assign it to a val, put it in a list, or pass it as a parameter. I prefer to think of this as treating logic as data. Instead of just defining a def, we can define a val that holds a function literal.

object DiscountRules {
  val seasonal: Double => Double = (price) => price * 0.9
  val vip: Double => Double = (price) => price * 0.8
  val none: Double => Double = (price) => price
}

Notice the syntax shift. We aren't using def anymore; we're using val. The type is Double => Double, which literally reads as "a function that takes a Double and returns a Double." Now, the logic is decoupled from the class structure. If I have a method that processes a batch of orders, I can pass the specific rule I want to use as an argument:

def applyDiscount(price: Double, strategy: Double => Double): Double = {
  strategy(price)
}

// Now I can just swap them out like Lego bricks
val finalPrice1 = applyDiscount(100.0, DiscountRules.vip)
val finalPrice2 = applyDiscount(100.0, DiscountRules.seasonal)

The cost of flexibility

You might be wondering why we don't just use val for everything. There is a trade-off here. Every time you define a function as a val, Scala creates an instance of a Function object (like Function1). This involves a tiny bit of memory overhead and a slight performance hit because you're performing an object invocation rather than a direct method call.

I usually stick to def for the "main" behavior of a class—the things that define what the class does. I switch to function values when I need to pass logic around, like in map, filter, or custom strategy patterns. If you see a def being used in a place where a function is expected, Scala will try to perform "eta-expansion"—essentially wrapping your method in a function object on the fly. It's a helpful convenience, but relying on it too much makes your code harder to reason about. When in doubt, ask yourself: "Is this a static capability of this object, or is this a piece of logic that needs to be portable?"




📋 Practical Task

Build a Dynamic Order Validator

You are building a checkout system where different orders require different validation rules (e.g., some orders must be over $20 to qualify for shipping, while others must have a valid tax ID).

Your Task:

  • Create a val named minAmountValidator that takes an Order (a simple case class with a total: Double) and returns a Boolean (true if total > 20.0).
  • Create a val named taxIdValidator that takes an Order (case class with a taxId: Option[String]) and returns true if the taxId is present.
  • Write a method called validateOrder that takes an Order and a validation function (as a parameter) and returns the result of that validation.
  • Demonstrate the validateOrder method working with both validators.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.