Last active
December 16, 2025 09:32
-
-
Save robinraju/b465380d152922b54cf2db22111548a6 to your computer and use it in GitHub Desktop.
Contextual Abstractions in Scala 3: A cleaner approach to implicits
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ============================================================================= | |
| // 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