Chiêu thức DefaultsTo trong Scala

414
07-03-2018
Chiêu thức DefaultsTo trong Scala

Giả sử bạn muốn viết hàm convert nhận 2 tham số là chuỗi kí tựkiểu dữ liệu, để biến chuỗi kí tự thành kiểu bạn cần.

Thực hiện bằng JavaScript

Trong JavaScript, bạn không thể thiết kế signature để hàm có dạng generic:

convert(String value)

Nếu như C# giữ được thông tin về generic lúc runtime, JavaScript có cơ chế type erasure để bỏ đi thông tin ấy, nhằm tương thích với phiên bản JavaScript cũ. Bạn phải thêm vào kiểu bạn cần như sau:

convert(String value, Class klass)

Làm bằng Scala Type tag

Trong Scala, bạn có thể giữ lại thông tin về generic lúc runtime. Lúc compile time Scala compiler khi compile sẽ lưu lại thông tin về generic vào bytecode, lúc runtime thư viện chuẩn Scala sẽ móc thông tin đó ra rồi truyền lại cho hàm bạn viết.

Bài viết trên dùng manifest đã bị deprecate từ Scala 2.10. Từ Scala 2.11 trở lên phải dùng type tag, như sau:

import scala.reflect.runtime.universe._

val TYPE_STRING = typeOf[String]

val TYPE_CHAR = typeOf[Char]

val TYPE_BOOLEAN = typeOf[Boolean]

val TYPE_BYTE = typeOf[Byte]

val TYPE_SHORT = typeOf[Short]

val TYPE_INT = typeOf[Int]

val TYPE_LONG = typeOf[Long]

val TYPE_FLOAT = typeOf[Float]

val TYPE_DOUBLE = typeOf[Double]

def convert[T: TypeTag](value: String): T = {

val t = typeOf[T]

val any: Any =

if (t <:< TYPE_INT) value.toInt

else if (t <:< TYPE_DOUBLE) value.toDouble

else if (t <:< TYPE_STRING) value

else throw new Exception("Cannot covert " value " to " t)

any.asInstanceOf[T]

}

// Usage examples

val x = convert[Int]("123")

val y = convert[Double]("1.23")

Chiêu thức DefaultsTo

Ở trên, mình trình bày cú pháp để lấy ra kiểu của tham số generic trong Scala. Hãy sử dụng đoạn mã sau:

convert("hi")

sẽ văng lỗi:

java.lang.NumberFormatException: For input string: "hi"

at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)

at java.lang.Integer.parseInt(Integer.java:580)

at java.lang.Integer.parseInt(Integer.java:615)

at scala.collection.immutable.StringLike$class.toInt(StringLike.scala:272)

at scala.collection.immutable.StringOps.toInt(StringOps.scala:30)

Nguyên nhân là khi không truyền T, mặc định nó sẽ là Nothing. Mà Nothing là kiểu đặc biệt, là con của tất cả các kiểu, nên sẽ cái if đầu tiên sẽ khớp, thành "hi".toInt. Trong khi mình muốn nếu không truyền T, nó sẽ mặc định là String.

Để làm được như vậy, bạn hãy dùng chiêu thức này:

sealed class DefaultsTo[A, B]

trait LowPriorityDefaultsTo {

implicit def overrideDefault[A, B] = new DefaultsTo[A, B]

}

object DefaultsTo extends LowPriorityDefaultsTo {

implicit def default[B] = new DefaultsTo[B, B]

}

DefaultsTo ở trên rồi, bạn sửa convert thành như sau là xong:

def convert[T: TypeTag](value: String)(implicit e: T DefaultsTo String): T

Ghi chú về :paste mode

Đoạn mã DefaultsTo trên nếu viết thành tập tin .scala thì không sao. Nhưng nếu copy paste vào màn hình Scala console để chạy thử thì sẽ bị báo:

warning: previously defined class DefaultsTo is not a companion to object DefaultsTo.

Companions must be defined together; you may wish to use :paste mode for this.

Cần dùng ":paste mode" như trong trong thông báo trên, để class DefaultsTo và object DefaultsTo thành đôi bạn cùng tiến.

>> Mời bạn xem tiêp: Scala: Class và Method

TAGS: scala
SHARE