2009-12-28 7 views
10

thời gian gần đây tôi phát hiện này ít scala example gọi phiên dịch đơn giản sử dụng monads:Điểm của việc sử dụng monads trong thông dịch viên là gì?

object simpleInterpreter { 

    case class M[A](value: A) { 
    def bind[B](k: A => M[B]): M[B] = k(value) 
    def map[B](f: A => B): M[B] = bind(x => unitM(f(x))) 
    def flatMap[B](f: A => M[B]): M[B] = bind(f) 
    } 

    def unitM[A](a: A): M[A] = M(a) 

    def showM(m: M[Value]): String = m.value.toString(); 

    type Name = String 

    trait Term; 
    case class Var(x: Name) extends Term 
    case class Con(n: int) extends Term 
    case class Add(l: Term, r: Term) extends Term 
    case class Lam(x: Name, body: Term) extends Term 
    case class App(fun: Term, arg: Term) extends Term 

    trait Value 
    case object Wrong extends Value { 
    override def toString() = "wrong" 
    } 
    case class Num(n: int) extends Value { 
    override def toString() = n.toString() 
    } 
    case class Fun(f: Value => M[Value]) extends Value { 
    override def toString() = "<function>" 
    } 

    type Environment = List[Pair[Name, Value]] 

    def lookup(x: Name, e: Environment): M[Value] = e match { 
    case List() => unitM(Wrong) 
    case Pair(y, b) :: e1 => if (x == y) unitM(b) else lookup(x, e1) 
    } 

    def add(a: Value, b: Value): M[Value] = Pair(a, b) match { 
    case Pair(Num(m), Num(n)) => unitM(Num(m + n)) 
    case _ => unitM(Wrong) 
    } 

    def apply(a: Value, b: Value): M[Value] = a match { 
    case Fun(k) => k(b) 
    case _ => unitM(Wrong) 
    } 

    def interp(t: Term, e: Environment): M[Value] = t match { 
    case Var(x) => lookup(x, e) 
    case Con(n) => unitM(Num(n)) 
    case Add(l, r) => for (val a <- interp(l, e); 
       val b <- interp(r, e); 
       val c <- add(a, b)) 
         yield c 
    case Lam(x, t) => unitM(Fun(a => interp(t, Pair(x, a) :: e))) 
    case App(f, t) => for (val a <- interp(f, e); 
       val b <- interp(t, e); 
       val c <- apply(a, b)) 
       yield c 
    } 

    def test(t: Term): String = 
    showM(interp(t, List())) 

    val term0 = App(Lam("x", Add(Var("x"), Var("x"))), Add(Con(10), Con(11))) 
    val term1 = App(Con(1), Con(2)) 

    def main(args: Array[String]) { 
    println(test(term0)) 
    println(test(term1)) 
    } 
} 

việc sử dụng/Ưu điểm của tính toán monadic đây là gì? Trên thực tế, M không là gì ngoài bản sắc đơn sắc. Đây có phải là chỉ giới thiệu để đưa ra một ví dụ về cú pháp monadic hoặc nó có một tác dụng quan trọng?

Trả lời

19

Dưới đây là một bản tóm tắt nhỏ về bài báo của Phil Wadler: Khi bạn viết một thông dịch viên theo phong cách "thẳng", rất nhiều mã phải thay đổi khi bạn thêm một tính năng mới. Ví dụ: nếu bạn thêm ngoại lệ, bạn phải kiểm tra xem có ngoại lệ nào được đặt lên bất kỳ vị trí nào bạn có thể đánh giá biểu thức hay không, ngay cả khi cấu trúc giống như nếu hoặc trong khi hoặc gọi hàm và không có gì liên quan đến ngoại lệ .

Nếu bạn viết trình thông dịch theo kiểu đơn sắc, bạn có thể thêm tính năng mới chỉ bằng cách thay đổi đơn nguyên. Bạn cũng thường thêm một vài bit mới của cú pháp để hỗ trợ tính năng này, nhưng không có phần còn lại của mã thay đổi. Vì vậy, phong cách đơn sắc là một cách để làm cho một thông dịch viên là mô-đun liên quan đến thay đổi ngôn ngữ.

Ví dụ:

  • Để thêm trường hợp ngoại lệ, thay đổi đơn nguyên với đơn nguyên lỗi, thêm cú pháp mới và mã cho throwcatch, và không ai trong số những thay đổi mã khác của bạn.

  • Để thay đổi ngôn ngữ sao cho giá trị của biểu thức là phân phối xác suất , không chỉ là giá trị, thay đổi đơn nguyên và thêm cấu trúc xác suất như "lật đồng xu thiên vị". Một lần nữa, không có mã cũ nào thay đổi. (Cái này là thực sự thú vị, tôi đã done it myself.)

Bây giờ mà tôi đã nói với bạn những gì lợi thế của tính toán monadic, Tốt hơn tôi nên nói với bạn những bất lợi tối thượng: bạn chỉ có thể làm một tính năng thú vị tại một thời điểm. Lý do là nói chung, bạn không thể sáng tác hai monads để tạo ra một đơn nguyên mới. Điều này là đúng không chỉ nói chung, nhưng của monads bạn thực sự có thể muốn sử dụng.

Nếu bạn thực sự quan tâm trong việc đưa ra một thông dịch viên mô-đun, trong đó bạn có thể dễ dàng thử nghiệm với kết hợp khác nhau các tính năng ngôn ngữ (như trái ngược với chỉ tính năng cá nhân), bạn cần biến đơn nguyên. Có một bài báo tuyệt vời trên Monad Transformers and Modular Interpreters bởi Sheng Liang, Paul Hudak và Mark Jones. Đó là một đọc tuyệt vời; Tôi khuyên bạn nên đánh giá cao.

4

Sử dụng đơn nguyên làm cho trình phân tích cú pháp/thông dịch viên có thể mở rộng. This paper by Philip Wadler mất một thời gian để đọc, nhưng khám phá ý tưởng này một cách chi tiết. Xem thêm Monadic Parsing in Haskell.

+1

Cảm ơn các liên kết. Nhưng bạn có thể vui lòng cụ thể hơn và tổng hợp những loại khả năng mở rộng nào được cấp bởi các monads? Thay thế ràng buộc danh tính bằng ví dụ: một đầu ra gỡ lỗi? Sử dụng thực tế là gì cho các trình phân tích cú pháp. Btw: Đoạn mã trên không liên quan gì đến các bộ phối hợp phân tích cú pháp đơn thuần (như trong ví dụ Haskell của bạn). – Dario

+0

Bạn đã đọc bài Wadler chưa? Ông đưa ra nhiều ví dụ khác nhau, đăng nhập là một trong số họ.Tôi biết bạn không phân tích cú pháp, nhưng ví dụ Wadler là giải thích, đó là, tôi tin rằng, những gì bạn đang làm. –