14

I've created akka actor called LogActor. The LogActors's receive method handling messages from other actors and logging them to the specified log level.

I can distinguish between the different levels in 2 ways. The first one:

import LogLevel._
object LogLevel extends Enumeration {
    type LogLevel = Value
    val Error, Warning, Info, Debug = Value
}
case class LogMessage(level : LogLevel, msg : String) 

The second: (EDIT)

abstract class LogMessage(msg : String)
case class LogMessageError(msg : String) extends LogMessage(msg)
case class LogMessageWarning(msg : String) extends LogMessage(msg)
case class LogMessageInfo(msg : String) extends LogMessage(msg)
case class LogMessageDebug(msg : String) extends LogMessage(msg)

Which way is more efficient? does it take less time to match case class or to match enum value?

(I read this question but there isn't any answer referring to the runtime issue)

Community
  • 1
  • 1
  • 1
    One important thing to keep in mind: in your (probably oversimplified) second snippet, you don't have a base class common to all ``xxxLogMessage`` case classes. – Malte Schwerhoff Nov 12 '12 at 12:35
  • So would it be better to do: case object LogMessage case class ErrorLogMessage(msg : String) extends LogMessage case class WarningLogMessage(msg : String) extends LogMessage case class InfoLogMessage(msg : String) extends LogMessage case class DebugLogMessage(msg : String) extends LogMessage? Is there aproproate way to write 'msg' as parameter of LogMessage (so it won't be necessary to write it in each case class..)? –  Nov 12 '12 at 12:53
  • 3
    you could do `abstract class LogMessage(msg:String); case class ErrorLogMessage(msg:String) extends LogMessage(msg); ...` But it won't save you from repeating `msg`. Btw, this is the correct way to do it, case classes inheritance is highly discouraged. – pedrofurla Nov 12 '12 at 13:01

3 Answers3

27

I totally agree with Alexey and Dennis that performance in this case shouldn't bother you, because it's more of a problem of compiler optimizations, not a developer, and I can't imagine a scenario where the performance difference could become noticeable.

What should bother you is your code consistency, and in this sense you should base your decision on whether you want to stick to the old java-ish approach with enums which is correctly described in your first example, or the lately growing popular Algebraic Data Types (ADT) pattern. The latter you've tried to represent in your second example but with some mistakes.

Following is how the problem could be solved with ADT pattern correctly.

ADT Solution #1

// 1. marked `sealed` to make pattern matching exhaustive
// 2. used a trait to avoid double storage of msg` and 
//    make the inheritance easier
sealed trait LogMessage { def msg : String }
// A better solution for isolation than names like "LogMessageError".
// Allows you to either address the members with a namespace like 
// "LogMessage.Error" or do "import LogMessage._" and address them 
// directly
object LogMessage { 
  case class Error (msg : String) extends LogMessage
  case class Warning (msg : String) extends LogMessage
  case class Info (msg : String) extends LogMessage
  case class Debug (msg : String) extends LogMessage
}

ADT Solution #2

Sorry for probably messing with your head, but it's worth noting that there also exists an alternative ADT approach for similar situations, which is kinda similar to the one you head with enums.

sealed trait LogLevel 
object LogLevel {
  case object Error extends LogLevel
  case object Warning extends LogLevel
  case object Info extends LogLevel
  case object Debug extends LogLevel
}
case class LogMessage ( level : LogLevel, msg : String )
Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169
8

"Don't optimize prematurely" applies. I don't believe difference between them is at all likely to matter, compared to time you spend passing messages to your actor or actually logging them. But I expect the best performance would be to create a Java enum (which can be easily accessed and used from Scala) for logging levels instead of Scala Enumeration.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
0

One of the most common operations for a logger is comparing current logging level to the level of a message, and with enums you get it for free, while with your setup with case classes it's going to be somewhat troublesome. And I agree with @AlexeyRomanov: matching shouldn't be a bottleneck here.

EDIT: performance-wise, match with case classes will use instanceof in bytecode, while for enums scala compiler generated code that none of the decompilers I tried could handle. It seemed to be using equals. So technically enums might be faster, but in reality there will be no difference in performance.

Denis Tulskiy
  • 19,012
  • 6
  • 50
  • 68