2009-04-08 32 views
9

Tôi đang xem xét việc chuyển một thư viện mẫu văn bản rất đơn giản sang scala, chủ yếu là tập thể dục trong việc học ngôn ngữ. Thư viện hiện đang triển khai tại cả hai Python và Javascript, và hoạt động cơ bản của nó nhiều hay ít nắm này (trong python):Tôi nên chỉ định loại dữ liệu không có cấu trúc giống như JSON trong Scala như thế nào?

template = CompiledTemplate('Text {spam} blah {eggs[1]}') 
data = { 'spam': 1, 'eggs': [ 'first', 'second', { 'key': 'value' }, true ] } 
output = template.render(data) 

Không ai trong số này là cực kỳ khó khăn để làm trong Scala, nhưng điều tôi m không rõ ràng là cách thể hiện tốt nhất loại tĩnh của thông số data. Về cơ bản, tham số này sẽ có thể chứa các thứ bạn tìm thấy trong JSON: một số nguyên thủy (chuỗi, int, booleans, null) hoặc danh sách 0 hoặc nhiều mục hoặc bản đồ bằng 0 hoặc nhiều hơn mặt hàng. (Vì mục đích của câu hỏi này, các bản đồ có thể bị ràng buộc để có các khóa chuỗi, có vẻ như cách Scala thích mọi thứ.)

Ý tưởng ban đầu của tôi chỉ là sử dụng một đối tượng cấp cao nhất, nhưng đó là dường như không hoàn toàn chính xác với tôi. Trong thực tế, tôi không muốn thêm các đối tượng tùy ý của bất kỳ loại lớp nào trong đó; Tôi chỉ muốn các yếu tố tôi đã nêu ở trên. Đồng thời, tôi nghĩ rằng trong Java gần nhất tôi thực sự có thể có được sẽ là Map<String, ?>, và tôi biết một trong những tác giả Scala thiết kế Generics của Java.

Một điều tôi đặc biệt tò mò là cách các ngôn ngữ chức năng khác với các hệ thống kiểu tương tự xử lý loại vấn đề này. Tôi có cảm giác rằng những gì tôi thực sự muốn làm ở đây là đưa ra một tập hợp các lớp vỏ mà tôi có thể phù hợp với mô hình, nhưng tôi không thể hình dung được nó trông như thế nào.

Tôi có Lập trình trong Scala, nhưng thành thật mà nói, đôi mắt của tôi bắt đầu lấp lánh một chút tại công cụ hiệp phương sai/contravariance và tôi hy vọng ai đó có thể giải thích điều này một chút rõ ràng và ngắn gọn hơn.

Trả lời

14

Bạn đang phát hiện ra rằng bạn muốn một số loại trường hợp lớp học để mô hình hóa các kiểu dữ liệu của bạn. Trong các ngôn ngữ chức năng, những thứ này được gọi là "Kiểu dữ liệu trừu tượng" và bạn có thể đọc tất cả về cách Haskell sử dụng chúng bằng cách Googling xung quanh một chút. Scala tương đương với ADT của Haskell sử dụng các đặc điểm và trường hợp kín.

Hãy xem số rewrite of the JSON parser combinator từ thư viện chuẩn Scala hoặc sách Lập trình trong Scala. Thay vì sử dụng Map [String, Any] để biểu diễn các đối tượng JSON và thay vì sử dụng Any để biểu thị các giá trị JSON tùy ý, nó sử dụng kiểu dữ liệu trừu tượng, JsValue, để represnt giá trị JSON. JsValue có một số loại phụ, thể hiện các loại giá trị JSON có thể có: JsString, JsNumber, JsObject, JsArray, JsBoolean (JsTrue, JsFalse) và JsNull.

Thao tác dữ liệu JSON của biểu mẫu này liên quan đến đối sánh mẫu. Kể từ khi JsValue được niêm phong, trình biên dịch sẽ cảnh báo bạn nếu bạn chưa xử lý tất cả các trường hợp. Ví dụ, mã cho toJson, một phương pháp mà phải mất một JsValue và trả về một đại diện String các giá trị đó, trông như thế này:

def toJson(x: JsValue): String = x match { 
    case JsNull => "null" 
    case JsBoolean(b) => b.toString 
    case JsString(s) => "\"" + s + "\"" 
    case JsNumber(n) => n.toString 
    case JsArray(xs) => xs.map(toJson).mkString("[",", ","]") 
    case JsObject(m) => m.map{case (key, value) => toJson(key) + " : " + toJson(value)}.mkString("{",", ","}") 
    } 

mẫu phù hợp với cả hai cho phép chúng tôi chắc chắn rằng chúng tôi đang làm việc với mọi trường hợp, và cũng "unwraps" giá trị cơ bản từ JsType của nó. Nó cung cấp một cách an toàn để biết rằng chúng tôi đã xử lý mọi trường hợp.

Hơn nữa, nếu bạn biết tại thời gian biên dịch cấu trúc của dữ liệu JSON bạn đang xử lý, bạn có thể làm điều gì đó thực sự tuyệt vời như n8han's extractors. Công cụ rất mạnh mẽ, hãy kiểm tra.

+0

Cảm ơn, đó chỉ là những gì tôi đang tìm kiếm. Nguồn của trình phân tích cú pháp JSON được viết lại mà bạn đã liên kết với trên pastebin là gì? (Tôi nhận thấy rằng trình phân tích cú pháp tích hợp trong libs của Scala chỉ sử dụng Map [String, Any].) –

+0

Tôi đã viết trình phân tích cú pháp liên kết với trên pastebin. Tôi đã có ý định thực hiện một dự án chính thức từ nó, nhưng không tìm thấy thời gian. –

1

Vâng, có một vài cách để tiếp cận điều này. Tôi có lẽ sẽ chỉ sử dụng Map[String, Any], mà sẽ chỉ làm việc tốt cho mục đích của bạn (miễn là bản đồ là từ collection.immutable thay vì collection.mutable). Tuy nhiên, nếu bạn thực sự muốn đi qua một số cơn đau, nó có thể cung cấp cho một loại cho việc này:

sealed trait InnerData[+A] { 
    val value: A 
} 

case class InnerString(value: String) extends InnerData[String] 
case class InnerMap[A, +B](value: Map[A, B]) extends InnerData[Map[A, B]] 
case class InnerBoolean(value: Boolean) extends InnerData[Boolean] 

Bây giờ, giả sử rằng bạn đã đọc lĩnh vực JSON data vào một lĩnh vực Scala tên jsData, bạn sẽ cung cấp cho lĩnh vực mà các loại sau đây:

val jsData: Map[String, Either[Int, InnerData[_]] 

Mỗi khi bạn kéo một lĩnh vực ra khỏi jsData, bạn sẽ cần phải phù hợp với mô hình, kiểm tra xem giá trị là kiểu Left[Int] hoặc Right[InnerData[_]] (hai tiểu loại Either[Int, InnerData[_]]). Khi bạn có dữ liệu bên trong, khi đó bạn sẽ khớp mẫu trên rằng để xác định xem nó có đại diện cho một số InnerString, InnerMap hoặc InnerBoolean hay không.

Về mặt kỹ thuật, bạn phải thực hiện loại đối sánh mẫu này để sử dụng dữ liệu khi bạn kéo nó ra khỏi JSON. Ưu điểm của phương pháp tiếp cận tốt là trình biên dịch sẽ kiểm tra bạn để đảm bảo rằng bạn không bỏ lỡ bất kỳ khả năng nào. Điểm bất lợi là bạn không thể bỏ qua những điều không thể (chẳng hạn như 'eggs' ánh xạ tới một số Int). Ngoài ra, có một số chi phí được áp đặt bởi tất cả các đối tượng bao bọc này, vì vậy hãy chú ý đến điều đó.

Lưu ý rằng Scala không cho phép bạn xác định một bí danh kiểu đó nên cắt giảm lượng Lộc cần cho việc này:

type DataType[A] = Map[String, Either[Int, InnerData[A]]] 

val jsData: DataType[_] 

Thêm một vài chuyển đổi tiềm ẩn để làm cho API xinh đẹp, và bạn sẽ có tất cả tốt đẹp và dandy.

+0

Bạn có thể xây dựng một chút về loại [Int, InnerData [A]] này không? Tôi không hiểu tại sao nó là Int, vì một điều, vì các nguyên thủy tôi muốn là từ tập hợp (Int, String, Boolean, null). Cảm ơn! –

+0

Hoặc trong Java :-) http://www.ibm.com/developerworks/java/library/j-ft13/index.html – ZiglioUK

1

JSON được sử dụng làm ví dụ trong "Lập trình trong Scala", trong chương về phân tích cú pháp kết hợp.

+0

Tôi đã thấy phần đó, nhưng các kiểu dữ liệu kết quả là một loại cây phân tích đến từ phân tích cú pháp chuỗi JSON và tôi không nhất thiết phải có chuỗi JSON bằng chữ để phân tích cú pháp; mã lệnh gọi 'render()' có thể có dữ liệu tùy ý mà nó được tập hợp từ một số nguồn khác. –

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