2013-03-29 33 views
9

Tôi thường gặp phải một mẫu hình vì vậy tôi đã tự hỏi liệu có phương pháp thuận tiện nào trong thư viện Scala cho nó hay không.Gọi lại một hàm cho đến khi nó trả về None

Để nó là hàm f: A => Option[B]. Tôi muốn thực hiện cuộc gọi lặp lại đến f bắt đầu bằng số bắt đầu x, f(f(f(x).get).get...), cho đến f trả về None và giữ giá trị không phải là None cuối cùng.

tôi đã viết một thực hiện cho việc này:

@tailrec 
def recurrentCallUntilNone[B](f: B => Option[B], x: B): B = f(x) match { 
    case Some(y) => recurrentCallUntilNone(f, y) 
    case None => x 
} 

Đây có phải là đã thực hiện trong thư viện chuẩn?

Ví dụ sử dụng cho điều này có thể là danh sách (Dây kéo) giữ vị trí hiện tại. Bằng cách gọi next, None được trả về nếu không có yếu tố nào sau vị trí hiện tại hoặc Option cho cùng một danh sách, nhưng với vị trí hiện tại tăng lên. Bằng cách sử dụng phương pháp trên, có thể xây dựng phương thức end để tìm kiếm danh sách ở cuối.

+0

Nó không có trong thư viện và bạn đang làm đúng cách. –

+2

thêm '@ tailrec'! –

+1

Đây gần như là một ['unfold'] (http://daily-scala.blogspot.co.at/2009/09/unfoldleft-and-right.html).Nhưng nó dường như không xảy ra trong bất kỳ libs nào. – phg

Trả lời

2

Những gì bạn đang làm trông giống như một kiểu rất cụ thể của trampoline. Trường hợp tổng quát hơn sử dụng các hàm được bọc trong trường hợp các lớp thay vì một số Option và hỗ trợ các kiểu đối số và trả về khác nhau.

Vì Calin-Andrei chỉ ra trampolines có sẵn trong thư viện chuẩn bằng cách sử dụng TailCalls object.

Từ mắt xích đầu tiên:

def even2(n: Int): Bounce[Boolean] = { 
    if (n == 0) Done(true) 
    else Call(() => odd2(n - 1)) 
} 
def odd2(n: Int): Bounce[Boolean] = { 
    if (n == 0) Done(false) 
    else Call(() => even2(n - 1)) 
} 
trampoline(even2(9999)) 

sealed trait Bounce[A] 
case class Done[A](result: A) extends Bounce[A] 
case class Call[A](thunk:() => Bounce[A]) extends Bounce[A] 

def trampoline[A](bounce: Bounce[A]): A = bounce match { 
    case Call(thunk) => trampoline(thunk()) 
    case Done(x) => x 
} 

Bây giờ với các thư viện chuẩn

import scala.util.control.TailCalls._ 

def even2(n: Int): TailRec[Boolean] = { 
    if (n == 0) done(true) 
    else tailcall(odd2(n - 1)) 
} 
def odd2(n: Int): TailRec[Boolean] = { 
    if (n == 0) done(false) 
    else tailcall(even2(n - 1)) 
} 
even2(9999).result 
+0

Tôi không biết về trampolines, vì vậy cảm ơn bạn vì điều đó. Tôi thấy rằng phiên bản hiện tại của thư viện Scala hỗ trợ chúng thông qua ['TailCalls'] (http://www.scala-lang.org/api/current/index.html#scala.util.control.TailCalls$). Câu trả lời của bạn là gần nhất với những gì tôi muốn. Bạn có thể sửa đổi nó sao cho nó sử dụng trampolines từ thư viện Scala? Sau đó, tôi có thể chấp nhận câu trả lời của bạn. –

+1

Wow Tôi không biết họ đã được thêm vào thư viện chuẩn. StackOverflow là tuyệt vời tôi trả lời một câu hỏi và tìm hiểu một cái gì đó mới cùng một lúc :-) – iain

1

trong trường hợp có cuộc thi sắc đẹp, bạn có thể xây dựng chức năng biến đổi hình ảnh hiện tại thành con quái vật mà bạn đang nói đến thông qua việc sử dụng currying.

def composeUntilTheEnd[B](f: Option[B] => Option[B])(x: Option[B]): Option[B] = 
     if (f(x) != None) composeUntilTheEnd(f)(f(x)) 
     else x 

def ff = composeUntilTheEnd((x:Option[Int]) => x)_ 

Bây giờ gọi ff bạn nhận được hành vi dự định.

+2

Tôi không nghĩ rằng điều này là đẹp hơn mà việc thực hiện được đưa ra bởi người hỏi. Ngoài ra, nếu chức năng đang tạo ra các hiệu ứng phụ, thì giải pháp này đang thực hiện f (x) hai lần có thể không được chấp nhận. – myyk

1

Nếu bạn muốn bạn có thể tạo hơi nước từ các tùy chọn của bạn và sau đó sử dụng các chức năng dòng trên đó để lấy phần tử được xác định cuối cùng. (Hay đúng hơn là phần tử được xác định cuối cùng trước phần tử không xác định)

def options[B](f: B => Option[B], initialValue: Option[B]): Stream[Option[B]] = { 
    initialValue #:: options(f, initialValue.map(f(_)).flatten) 
} 

options.takeWhile(_.isDefined).last 
2

Làm thế nào về:

Stream.iterate(Some(x)) { x => x.flatMap(f _) }.takeWhile { _.isDefined }.last 

CẬP NHẬT

Hoặc thậm chí neater IMHO (chỉ traversal duy nhất):

val result = { 
    val xs = Stream.iterate(Some(x)) { x => x.flatMap(f _) } 
    (xs zip xs.tail) collectFirst { 
    case (Some(x), None) => x 
    } get 
} 

Lưu ý rằng nó là an toàn để gọi collectFirst vì chúng ta không thể bắt đầu với một None (nếu không một vòng lặp vô hạn sẽ có thể).

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