2012-03-12 28 views
19

Khi lập trình bằng java, tôi luôn ghi thông số đầu vào và giá trị trả về của phương thức, nhưng trong scala, dòng cuối cùng của phương thức là giá trị trả về. vì vậy tôi phải làm một cái gì đó như:cách giữ giá trị trả về khi đăng nhập scala

def myFunc() = { 
    val rs = calcSomeResult() 
    logger.info("result is:" + rs) 
    rs 
} 

để làm cho nó dễ dàng, tôi viết một tiện ích:

class LogUtil(val f: (String) => Unit) { 
def logWithValue[T](msg: String, value: T): T = { f(msg); value } 
} 

object LogUtil { 
    def withValue[T](f: String => Unit): ((String, T) => T) = new LogUtil(f).logWithValue _ 
} 

Sau đó, tôi sử dụng nó như là:

val rs = calcSomeResult() 
withValue(logger.info)("result is:" + rs, rs) 

nó sẽ đăng nhập giá trị và trả lại. nó có hiệu quả đối với tôi, nhưng có vẻ lạ lùng. như tôi là một lập trình viên java cũ, nhưng mới với scala, tôi không biết liệu có một cách thành ngữ hơn để làm điều này trong scala.


nhờ sự giúp đỡ của bạn, bây giờ tôi có thể tạo một tốt hơn thông số util sử dụng Kestrel combinator metioned bởi romusz

object LogUtil { 
    def kestrel[A](x: A)(f: A => Unit): A = { f(x); x } 
    def logV[A](f: String => Unit)(s: String, x: A) = kestrel(x) { y => f(s + ": " + y)} 
} 

tôi thêm f để tôi có thể vượt qua nó một logger từ slf4j, và các trường hợp thử nghiệm là:

class LogUtilSpec extends FlatSpec with ShouldMatchers { 
    val logger = LoggerFactory.getLogger(this.getClass()) 
    import LogUtil._ 

"LogUtil" should "print log info and keep the value, and the calc for value should only be called once" in { 
    def calcValue = { println("calcValue"); 100 } // to confirm it's called only once 
    val v = logV(logger.info)("result is", calcValue) 
    v should be === 100 
    } 
} 
+0

Tại sao không sử dụng AspectJ? Vâng, nó không phải là Scala, đó là sự thật. –

Trả lời

6

Bạn có ý tưởng cơ bản đúng - bạn chỉ cần dọn dẹp nó một chút để làm cho nó tiện lợi tối đa.

class GenericLogger[A](a: A) { 
    def log(logger: String => Unit)(str: A => String): A = { logger(str(a)); a } 
} 
implicit def anything_can_log[A](a: A) = new GenericLogger(a) 

Bây giờ bạn có thể

scala> (47+92).log(println)("The answer is " + _) 
The answer is 139 
res0: Int = 139 

Bằng cách này bạn không cần phải lặp lại chính mình (ví dụ không rs hai lần).

+0

wow, điều này thật tuyệt! cảm ơn bạn! –

7

Nếu bạn thích một cách tiếp cận chung chung hơn tốt hơn, bạn có thể định nghĩa

implicit def idToSideEffect[A](a: A) = new { 
    def withSideEffect(fun: A => Unit): A = { fun(a); a } 
    def |!>(fun: A => Unit): A = withSideEffect(fun) // forward pipe-like 
    def tap(fun: A => Unit): A = withSideEffect(fun) // public demand & ruby standard 
} 

và sử dụng nó như

calcSomeResult() |!> { rs => logger.info("result is:" + rs) } 

calcSomeResult() tap println 
+0

cảm ơn bạn. có vẻ tốt. Tôi đoán tôi phải đọc thêm về chuyển đổi ngầm ... –

+5

Thay vì một biểu tượng lạ, bạn có thể cùng một số ký tự xác định 'tap', thực hiện chính xác điều tương tự trong Ruby. –

+0

Cảm ơn, @RexKerr. Tôi đã tìm kiếm một chút về tên nhưng không nghĩ đến Ruby. – Debilski

34

gì bạn đang tìm kiếm được gọi là Kestrel combinator (K combinator): Kxy = x . Bạn có thể thực hiện tất cả các loại tác vụ phụ (không chỉ ghi nhật ký) trong khi trả lại giá trị được chuyển cho nó. Đọc https://github.com/raganwald/homoiconic/blob/master/2008-10-29/kestrel.markdown#readme

Trong Scala cách đơn giản nhất để thực hiện nó là:

def kestrel[A](x: A)(f: A => Unit): A = { f(x); x } 

Sau đó, bạn có thể xác định chức năng in/đăng nhập của bạn như:

def logging[A](x: A) = kestrel(x)(println) 
def logging[A](s: String, x: A) = kestrel(x){ y => println(s + ": " + y) } 

Và sử dụng nó như:

logging(1 + 2) + logging(3 + 4) 

chức năng mẫu của bạn trở thành một lớp lót:

Nếu bạn thích ký hiệu OO, bạn có thể sử dụng hàm ý như trong các câu trả lời khác, nhưng vấn đề với cách tiếp cận này là bạn sẽ tạo một đối tượng mới mỗi khi bạn muốn ghi lại điều gì đó. bạn làm điều đó thường xuyên đủ.Nhưng cho đầy đủ, nó trông như thế này:

implicit def anyToLogging[A](a: A) = new { 
    def log = logging(a) 
    def log(msg: String) = logging(msg, a) 
} 

sử dụng nó như:

def myFunc() = calcSomeResult().log("result is") 
+3

+1 để làm nổi bật chi phí bổ sung của giải pháp OO khi so sánh với hàm tương đương. Tôi tin rằng chức năng ghi nhật ký phải được sử dụng một cách tiết kiệm và trong các trường hợp như vậy, cú pháp thuận tiện có thể được ưu tiên hơn tốc độ hoặc sử dụng bộ nhớ. –

+1

cảm ơn vì kiến ​​thức về Kestrel combinator, tôi có thể có một util tốt hơn bây giờ –

+0

@ 诺 铁 Bạn được chào đón. – romusz

3

Hãy nói rằng bạn đã có một lớp cơ sở cho tất cả các bạn logger:

abstract class Logger { 
    def info(msg:String):Unit 
} 

Sau đó, bạn có thể mở rộng Chuỗi bằng phương thức ghi nhật ký @@:

object ExpressionLog { 
    // default logger 
    implicit val logger = new Logger { 
    def info(s:String) {println(s)} 
    } 

    // adding @@ method to all String objects 
    implicit def stringToLog (msg: String) (implicit logger: Logger) = new { 
    def @@ [T] (exp: T) = { 
     logger.info(msg + " = " + exp) 
     exp 
    } 
    } 
} 
.210

Để sử dụng đăng nhập bạn sẽ phải nhập khẩu các thành viên của ExpressionLog đối tượng và sau đó bạn có thể dễ dàng đăng nhập biểu thức sử dụng các ký hiệu sau:

import ExpressionLog._ 

def sum (a:Int, b:Int) = "sum result" @@ (a+b) 
val c = sum("a" @@ 1, "b" @@2) 

Sẽ in:

a = 1 
b = 2 
sum result = 3 

Điều này hoạt động bởi vì mỗi khi bạn gọi phương thức @@ trên trình biên dịch String nhận ra rằng String không có phương thức và chuyển đổi âm thầm đưa nó vào một đối tượng với kiểu ẩn danh có phương thức @@ được xác định (xem stringToLog). Là một phần của trình biên dịch chuyển đổi, hãy chọn trình ghi nhật ký mong muốn như là implicit parameter, theo cách này, bạn không phải tiếp tục chuyển logger đến @@ mỗi lần bạn vẫn giữ quyền kiểm soát toàn bộ trình ghi nhật ký cần được sử dụng mọi lúc.

Theo ưu tiên, khi phương pháp @@ được sử dụng trong ký hiệu infix, nó có số highest priority giúp bạn dễ dàng hơn lý do về nội dung sẽ được ghi lại.

Vì vậy, nếu bạn muốn sử dụng trình ghi nhật ký khác nhau theo một trong các phương pháp của mình thì sao? Điều này rất đơn giản:

import ExpressionLog.{logger=>_,_} // import everything but default logger 
// define specific local logger 
// this can be as simple as: implicit val logger = new MyLogger 
implicit val logger = new Logger { 
    var lineno = 1 
    def info(s:String) { 
    println("%03d".format(lineno) + ": " + s) 
    lineno+=1 
    } 
} 

// start logging 
def sum (a:Int, b:Int) = a+b 
val c = "sum result" @@ sum("a" @@ 1, "b" @@2) 

Will đầu ra:

001: a = 1 
002: b = 2 
003: sum result = 3 
1

Biên dịch tất cả các câu trả lời, ưu và nhược điểm, tôi đã đưa ra với điều này (bối cảnh là một ứng dụng Play):

import play.api.LoggerLike 

object LogUtils { 

implicit class LogAny2[T](val value : T) extends AnyVal { 

    def @@(str : String)(implicit logger : LoggerLike) : T = { 
     logger.debug(str); 
     value 
    } 

    def @@(f : T => String)(implicit logger : LoggerLike) : T = { 
     logger.debug(f(value)) 
     value 
    } 
} 

Như bạn có thể thấy, LogAny là một AnyVal nên không có bất kỳ chi phí nào của việc tạo đối tượng mới.

Bạn có thể sử dụng nó như thế này:

scala> import utils.LogUtils._ 
scala> val a = 5 
scala> val b = 7 
scala> implicit val logger = play.api.Logger 

scala> val c = a + b @@ { c => s"result of $a + $b = $c" } 
c: Int = 12 

Hoặc nếu bạn không cần một tham chiếu đến kết quả, chỉ cần sử dụng:

scala> val c = a + b @@ "Finished this very complex calculation" 
c: Int = 12 

Bất kỳ nhược điểm để thực hiện điều này?

Edit:

Tôi đã làm sẵn này với một số cải tiến trong một gist here

Các vấn đề liên quan