Skip to content

Instantly share code, notes, and snippets.

@robinraju
Last active December 16, 2025 09:32
Show Gist options
  • Select an option

  • Save robinraju/b465380d152922b54cf2db22111548a6 to your computer and use it in GitHub Desktop.

Select an option

Save robinraju/b465380d152922b54cf2db22111548a6 to your computer and use it in GitHub Desktop.
Contextual Abstractions in Scala 3: A cleaner approach to implicits
// =============================================================================
// Run with: scala ContextualAbstractionsDemo.scala
// =============================================================================
//> using scala 3.3.7
//> using options -Wunused:imports
import java.time.Instant
import scala.language.implicitConversions
// -----------------------------------------------------------------------------
// Domain Types
// -----------------------------------------------------------------------------
case class Stock(symbol: String, name: String, price: BigDecimal, marketCap: Long):
override def toString: String = s"$symbol ($name) @ $$${price}"
enum OrderType:
case Buy, Sell
case class Order(stockSymbol: String, quantity: Int, orderType: OrderType)
case class USD(amount: BigDecimal):
override def toString: String = s"$$${amount} USD"
case class EUR(amount: BigDecimal):
override def toString: String = s"€${amount} EUR"
// -----------------------------------------------------------------------------
// Given Instances - Defining canonical values
// -----------------------------------------------------------------------------
// Default ordering for Stock (only one given in scope to avoid ambiguity)
given stockByPrice: Ordering[Stock] = Ordering.by(_.price)
// Alternative orderings kept in an object - import explicitly when needed
object StockOrderings:
given byMarketCap: Ordering[Stock] = Ordering.by(_.marketCap)
given bySymbol: Ordering[Stock] = Ordering.by(_.symbol)
// -----------------------------------------------------------------------------
// Context Types and Given Instances
// -----------------------------------------------------------------------------
trait TradingContext:
def marketOpen: Boolean
def defaultCurrency: String
def maxOrderSize: Int
trait PricingStrategy:
def calculateTotalCost(stock: Stock, quantity: Int): BigDecimal
trait RiskCalculator:
def assessRisk(order: Order): String
// Default implementations as given instances
given defaultContext: TradingContext with
def marketOpen: Boolean = true
def defaultCurrency: String = "USD"
def maxOrderSize: Int = 10000
given standardPricing: PricingStrategy with
def calculateTotalCost(stock: Stock, quantity: Int): BigDecimal =
stock.price * quantity * BigDecimal("1.001") // 0.1% trading fee
given defaultRiskCalculator: RiskCalculator with
def assessRisk(order: Order): String =
if order.quantity > 1000 then "HIGH"
else if order.quantity > 100 then "MEDIUM"
else "LOW"
// -----------------------------------------------------------------------------
// Using Clauses - Functions that require context
// -----------------------------------------------------------------------------
def validateOrder(order: Order)(using ctx: TradingContext): Boolean =
ctx.marketOpen && order.quantity <= ctx.maxOrderSize
def executeOrder(order: Order)(using ctx: TradingContext, pricing: PricingStrategy): Unit =
if validateOrder(order) then
println(s"Order validated in ${ctx.defaultCurrency}")
else
println(s"Order validation failed")
def processOrder(order: Order)(using TradingContext): Unit =
// Summon the RiskCalculator from context
val riskCalc = summon[RiskCalculator]
val riskLevel = riskCalc.assessRisk(order)
println(s"Risk level: $riskLevel")
// Context bounds shorthand
def sortStocks[T: Ordering](items: List[T]): List[T] = items.sorted
// -----------------------------------------------------------------------------
// Contextual Conversions with Conversion[-T, +U]
// -----------------------------------------------------------------------------
object CurrencyConversions:
private val usdToEurRate = BigDecimal("0.85")
private val eurToUsdRate = BigDecimal("1.18")
given Conversion[USD, EUR] = usd => EUR((usd.amount * usdToEurRate).setScale(2, BigDecimal.RoundingMode.HALF_UP))
given Conversion[EUR, USD] = eur => USD((eur.amount * eurToUsdRate).setScale(2, BigDecimal.RoundingMode.HALF_UP))
// Order request to executed order conversion
case class OrderRequest(symbol: String, quantity: Int, orderType: OrderType)
case class ExecutedOrder(
symbol: String,
quantity: Int,
orderType: OrderType,
executedAt: Instant,
status: String
)
given Conversion[OrderRequest, ExecutedOrder] with
def apply(req: OrderRequest): ExecutedOrder =
ExecutedOrder(
symbol = req.symbol,
quantity = req.quantity,
orderType = req.orderType,
executedAt = Instant.now(),
status = "EXECUTED"
)
def recordTrade(executed: ExecutedOrder): Unit =
println(s"Recorded: ${executed.symbol} - ${executed.status} at ${executed.executedAt}")
// -----------------------------------------------------------------------------
// Main - Demonstrating all concepts
// -----------------------------------------------------------------------------
@main def runDemo(): Unit =
println("=" * 70)
println("Contextual Abstractions in Scala 3 - Demo")
println("=" * 70)
// Sample data
val stocks = List(
Stock("AAPL", "Apple", BigDecimal("178.50"), 2800000000000L),
Stock("MSFT", "Microsoft", BigDecimal("378.91"), 2810000000000L),
Stock("GOOGL", "Alphabet", BigDecimal("141.80"), 1780000000000L),
Stock("AMZN", "Amazon", BigDecimal("178.25"), 1850000000000L),
Stock("NVDA", "NVIDIA", BigDecimal("875.28"), 2160000000000L)
)
// -------------------------------------------------------------------------
// 1. Given Instances - Sorting with different orderings
// -------------------------------------------------------------------------
println("1. GIVEN INSTANCES - Multiple Orderings")
println("-" * 50)
println("Sorted by price (default given):")
stocks.sorted.foreach(s => println(s" $s"))
println("\nSorted by market cap (explicit using):")
stocks.sorted(using StockOrderings.byMarketCap).foreach(s => println(s" $s (cap: ${s.marketCap / 1_000_000_000}B)"))
println("\n Sorted by symbol:")
stocks.sorted(using StockOrderings.bySymbol).foreach(s => println(s" $s"))
// -------------------------------------------------------------------------
// 2. Using Clauses - Context parameters
// -------------------------------------------------------------------------
println("\n2. USING CLAUSES - Context Parameters")
println("-" * 50)
val order1 = Order("AAPL", 100, OrderType.Buy)
val order2 = Order("MSFT", 15000, OrderType.Buy) // Exceeds max order size
println(s"\nProcessing order: ${order1}")
executeOrder(order1) // Uses defaultContext and standardPricing automatically
processOrder(order1)
println(s"\nProcessing large order: ${order2}")
executeOrder(order2)
processOrder(order2)
{
given closedMarket: TradingContext with
def marketOpen: Boolean = false
def defaultCurrency: String = "USD"
def maxOrderSize: Int = 5000
println(s"\nProcessing order with closed market:")
executeOrder(order1) // Now uses closedMarket within this block
}
// -------------------------------------------------------------------------
// 3. Summon - Retrieving contextual values
// -------------------------------------------------------------------------
println("\n3. SUMMON - Retrieving Contextual Values")
println("-" * 50)
def demonstrateSummon(): Unit =
val ordering = summon[Ordering[Stock]]
val ctx = summon[TradingContext]
val pricing = summon[PricingStrategy]
println(s"Summoned TradingContext: market open = ${ctx.marketOpen}")
println(s"Summoned PricingStrategy: cost for 10 AAPL = ${pricing.calculateTotalCost(stocks.head, 10)}")
demonstrateSummon() //(using defaultContext, standardPricing)
// -------------------------------------------------------------------------
// 4. Conversion - Type-safe implicit conversions
// -------------------------------------------------------------------------
println("\n4. CONVERSION - Type-safe Currency Conversions")
println("-" * 50)
import CurrencyConversions.given
val dollars = USD(BigDecimal("100.00"))
val euros: EUR = dollars // Automatic conversion
val backToDollars: USD = euros // And back
println(s"Original: $dollars")
println(s"Converted to EUR: $euros")
println(s"Converted back to USD: $backToDollars")
// Order conversion
println("Order Request to Executed Order conversion:")
val request = OrderRequest("NVDA", 50, OrderType.Buy)
println(s"Request: ${request}")
recordTrade(request) // Automatic conversion to ExecutedOrder
// -------------------------------------------------------------------------
// 5. Context Bounds - Shorthand syntax
// -------------------------------------------------------------------------
println("\n5. CONTEXT BOUNDS - Shorthand Syntax")
println("-" * 50)
// Using the context bounds version of sortStocks
val sortedByPrice = sortStocks(stocks)(using stockByPrice)
println("Using context bounds [T: Ordering]:")
println(s"First stock by price: ${sortedByPrice.head}")
println(s"Last stock by price: ${sortedByPrice.last}")
println("\n" + "=" * 70)
println("Demo complete!")
println("=" * 70)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment