2011-10-16 34 views
9

Trong this recent Stack Overflow question, tác giả muốn thay đổi danh sách các trình phân tích cú pháp của một số loại thành một trình phân tích cú pháp trả về các danh sách thuộc loại đó. Chúng ta có thể tưởng tượng làm điều này với Scalaz của sequence cho functors applicative:Viết thể loại lớp cho các lớp lồng nhau trong Scala

import scala.util.parsing.combinator._ 

import scalaz._ 
import Scalaz._ 

object parser extends RegexParsers { 
    val parsers = List(1, 2, 3).map(repN(_, """\d+""".r)) 
    def apply(s: String) = parseAll(parsers.sequence, s) 
} 

Ở đây chúng ta phải mất một danh sách ba parsers rằng trở lại danh sách các số nguyên và biến nó thành một phân tích cú pháp trả về danh sách liệt kê các số nguyên. Thật không may Scalaz không cung cấp một ví dụ Applicative cho Parser, do đó, mã này không biên dịch, nhưng đó là dễ dàng để sửa chữa:

import scala.util.parsing.combinator._ 

import scalaz._ 
import Scalaz._ 

object parser extends RegexParsers { 
    val parsers = List(1, 2, 3).map(repN(_, """\d+""".r)) 
    def apply(s: String) = parseAll(parsers.sequence, s) 

    implicit def ParserPure: Pure[Parser] = new Pure[Parser] { 
    def pure[A](a: => A) = success(a) 
    } 

    implicit def ParserFunctor: Functor[Parser] = new Functor[Parser] { 
    def fmap[A, B](p: Parser[A], f: A => B) = p.map(f) 
    } 

    implicit def ParserBind: Bind[Parser] = new Bind[Parser] { 
    def bind[A, B](p: Parser[A], f: A => Parser[B]) = p.flatMap(f) 
    } 
} 

này hoạt động như mong đợi: parser("1 2 3 4 5 6") cho chúng ta List(List(1), List(2, 3), List(4, 5, 6)), ví dụ.

(Tôi biết tôi chỉ có thể đưa ra một ví dụ Apply, nhưng trường hợp Bind ngắn gọn hơn.)

Nó sẽ được tốt đẹp để không phải làm điều này mỗi khi chúng tôi mở rộng Parsers, nhưng tôi không rõ ràng về cách nhận được phiên bản Applicative cho số Parsers#Parser thường hơn. Cách tiếp cận ngây thơ sau đây tất nhiên không làm việc, kể từ khi chúng ta cần các trường hợp Parsers là giống nhau:

implicit def ParserBind: Bind[Parsers#Parser] = new Bind[Parsers#Parser] { 
    def bind[A, B](p: Parsers#Parser[A], f: A => Parsers#Parser[B]) = p.flatMap(f) 
} 

Nó khá rõ ràng với tôi rằng điều này nên có thể, nhưng tôi không cảm thấy thoải mái đủ với Scala của loại hệ thống để biết làm thế nào để đi về nó. Có cái gì đơn giản mà tôi đang thiếu?


Để đối phó với các câu trả lời dưới đây: Tôi đã thử các tuyến đường -Ydependent-method-types, và nhận được điều này cho đến nay:

implicit def ParserApplicative(g: Parsers): Applicative[g.Parser] = { 
    val f = new Functor[g.Parser] { 
    def fmap[A, B](parser: g.Parser[A], f: A => B) = parser.map(f) 
    } 

    val b = new Bind[g.Parser] { 
    def bind[A, B](p: g.Parser[A], f: A => g.Parser[B]) = p.flatMap(f) 
    } 

    val p = new Pure[g.Parser] { 
    def pure[A](a: => A) = g.success(a) 
    } 

    Applicative.applicative[g.Parser](p, FunctorBindApply[g.Parser](f, b)) 
} 

Vấn đề (như didierd chỉ ra) là nó không rõ ràng như thế nào để có được những implicit để bắt đầu. Cách tiếp cận này hoạt động, nhưng bạn phải thêm một số thông tin như sau vào ngữ pháp của mình:

implicit val applicative = ParserApplicative(this) 

Tại thời điểm đó, trộn trong cách tiếp cận rõ ràng là hấp dẫn hơn nhiều.

(Lưu ý: Tôi dự kiến ​​có thể viết đơn giản là Applicative.applicative[g.Parser] ở trên, nhưng điều đó cho biết lỗi trình biên dịch không thể tìm thấy giá trị tiềm ẩn cho Pure[g.Parser] — mặc dù một người đang ngồi ngay bên cạnh nó. vì vậy, rõ ràng có điều gì đó khác nhau về cách thức implicits làm việc với nhiều loại phương pháp phụ thuộc.)


Nhờ retronym để chỉ ra một thủ thuật mà hoàn thành những gì tôi muốn ở đây. Tôi đã tóm tắt như sau từ his code:

implicit def parserMonad[G <: Parsers with Singleton] = 
    new Monad[({ type L[T] = G#Parser[T] })#L] { 
    def pure[A](a: => A): G#Parser[A] = { 
     object dummy extends Parsers 
     dummy.success(a).asInstanceOf[G#Parser[A]] 
    } 

    def bind[A, B](p: G#Parser[A], f: (A) => G#Parser[B]): G#Parser[B] = 
     p.flatMap(f) 
    } 

Nếu bạn có điều này trong phạm vi, bạn sẽ có được một trường hợp đơn nguyên cho Parser ở bất kỳ đối tượng mở rộng Parsers. Đó là loại gian lận vì dàn diễn viên, nhưng vẫn khá gọn gàng.

Trả lời

4

tôi thường thêm phần mở rộng ngầm để Parser trong mixins cho Parsers

trait BindForParser extends Parsers { 
    implicit def ParserBind = new Bind[Parser] { 
    def bind[A,B](p: Parser[A], f: A => Parser[B]) = p flatMap f 
    } 
} 

Sau đó, bạn chỉ cần trộn rằng trong ngữ pháp của bạn (Parsers), và như Parser trường hợp thường được chế tác chỉ bên Parsers, không có nhiều khả năng mixin sẽ là cần thiết sau đó, khi ngữ pháp được thực hiện và bạn không còn có thể kết hợp một cái gì đó trong. Trong ví dụ của bạn, bạn chỉ cần làm

object parser extends Parsers with BindForParser 

Về câu hỏi tổng quát hơn, cho dù đó là khả năng để làm điều đó "từ bên ngoài", cách trực tiếp nhất có lẽ sẽ là một cái gì đó giống như

implicit def ParserBind(grammar: Parsers) = new Bind[grammar.Parser] { 
    def bind[A,B](p: grammar.Parser[A], f: A => grammar.Parser[B]) = p flatMap f 
} 

Nhưng điều này là không được phép, một số phương pháp (ở đây grammar) không được coi là số nhận dạng ổn định và do đó, grammar.Parser không được phép làm loại. Tuy nhiên nó có thể với tùy chọn -Xexperimental. Nhưng ngay cả sau đó, tôi không thấy làm thế nào tiềm ẩn sẽ đá vào khi cần thiết. Những gì chúng ta muốn là một Bind ngầm định [ngữ pháp], và với tham số ngữ pháp, đây không phải là những gì chúng ta có.

Vì vậy, câu trả lời của tôi sẽ là nó không thể được thực hiện, nhưng tôi sẽ không ngạc nhiên nếu ai đó có thể nghĩ ra điều gì đó.

2

Xử lý các loại phụ thuộc đường dẫn khá phức tạp. Dưới đây là một cách:

https://github.com/retronym/scalaz7-experimental/commit/8bf1d2a090cf56d33e11c554e974ea3c82b7b37f

+0

Đây là thông minh và đẹp hơn nhiều so với phụ thuộc phiên bản loại phương pháp của tôi, nhưng tôi vẫn muốn làm mà không có 'ngầm val M: Monad [Parser] = parserMonad (testParser) ' . Bạn có nghĩ rằng điều đó là không thể? –

+1

Tôi cần ví dụ về 'Trình phân tích cú pháp' để gọi' thành công'. Bạn có thể làm cho chính 'trình phân tích cú pháp' ẩn chứa nó để cung cấp cho nó' parserMonad', nhưng điều đó không có vẻ giống như một ý tưởng hay. – retronym

+1

Nếu bạn sẵn sàng thừa nhận một 'asInstanceOf', bạn thực sự có thể thực hiện việc này: https://github.com/retronym/scalaz7-experimental/commit/aa80e4792799a509c728eecff771ec74518720e7 – retronym

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