Scala
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Scala
-
Section 4: Functional Scala
-
Section 5: Collections in Depth
-
Section 6: Type System
-
Section 7: Concurrency and Ecosystem
-
Section 8: Practical Projects
-
Section 9: Interview Practice
-
Section 10: Data Structures and Algorithms in Scala
-
Section 11: More Practice Exercises
-
Section 12: Advanced Functional Patterns
-
Section 13: More Ecosystem
-
Section 14: Scala Collections Library Deep Dive
-
Section 15: Scala Standard Library Deep Dive
-
Section 16: Akka Ecosystem Deep Dive
-
Section 17: Cats and Cats Effect Deep Dive
-
Section 18: Play Framework Deep Dive
-
Section 19: Apache Spark with Scala Deep Dive
-
Section 20: Scala Build Tools Deep Dive
-
Section 21: Scala 3 Specific Features
-
90: Union and Intersection Types
-
Section 22: Scala Testing Deep Dive
-
Section 23: Functional Domain Modeling
-
Section 24: More Data Structures and Algorithms in Scala
-
Section 25: Scala for Data Engineering
-
Section 26: More Practical Projects
-
Section 27: More Interview and Review
-
Section 28: ZIO Ecosystem Deep Dive
-
Section 29: Scala for Machine Learning
-
Section 30: Scala Microservices Architecture
-
Section 31: Scala Type System Deep Dive
-
Section 32: More Practice and Drills
-
Section 33: Scala Performance Deep Dive
-
Section 34: Scala Ecosystem Tooling
-
Section 35: Scala for Reactive Systems
-
Section 36: More Real-World Case Studies
-
Section 37: Scala for Financial Systems
-
Section 38: Scala GraphQL and gRPC
-
Section 39: More Final Projects
-
Section 40: More Interview and Final Review
-
Section 41: Scala for Streaming Data
-
Section 42: Scala Security Practices
-
Section 43: More Language Deep Dive
-
Section 44: Scala Command-Line Tools
-
Section 45: Scala Documentation and Style
-
Section 46: Scala Dependency Management
-
Section 47: More Practical Backend Patterns
-
Section 48: Scala for Event-Driven Architecture
-
Section 49: More Practice Drills Round 2
-
Section 50: Scala Compiler Deep Dive
-
Section 51: Scala for Web Frontends
-
Section 52: More Data Engineering Practice
-
Section 53: Scala Observability
-
Section 54: More Advanced Practice Projects
-
Section 55: Scala for Legacy Java Integration
-
Section 56: More Testing Practice
-
Section 57: Final Mastery Review
-
Section 58: Scala History and Ecosystem Context
-
Section 59: More Concurrency Patterns
-
Section 60: Scala for Configuration Management
-
Section 61: More Domain Modeling Practice
7: Functions and Methods
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
valnamedminAmountValidatorthat takes anOrder(a simple case class with atotal: Double) and returns aBoolean(true if total > 20.0). - Create a
valnamedtaxIdValidatorthat takes anOrder(case class with ataxId: Option[String]) and returnstrueif thetaxIdis present. - Write a method called
validateOrderthat takes anOrderand a validation function (as a parameter) and returns the result of that validation. - Demonstrate the
validateOrdermethod working with both validators.
There are no comments for now.