2013-10-30 15 views
22

Tôi đã xem một video gần đây về cách bạn có thể tìm ra đơn nguyên IO, cuộc trò chuyện diễn ra trong scala. Tôi thực sự tự hỏi những gì các điểm có chức năng trả lại IO [A] trong số họ. Các biểu thức lambda được bọc trong đối tượng IO là những đột biến và tại một số điểm cao hơn sự thay đổi chúng phải được quan sát, ý tôi là đã được thực hiện, để một cái gì đó xảy ra. Bạn không chỉ đẩy vấn đề lên cao hơn cái cây ở nơi khác?Scala IO monad: điểm là gì?

Lợi ích duy nhất tôi có thể thấy là nó cho phép đánh giá lười biếng, theo nghĩa là nếu bạn không gọi hoạt động unsafePerformIO không có tác dụng phụ xảy ra. Ngoài ra tôi đoán các phần khác của chương trình có thể sử dụng/chia sẻ mã và deciede khi nó muốn các tác dụng phụ xảy ra.

Tôi đã tự hỏi nếu điều này là tất cả? Có lợi thế trong testability? Tôi giả sử không phải là bạn sẽ phải quan sát các hiệu ứng mà loại phủ nhận điều này. Nếu bạn đã sử dụng các đặc điểm/giao diện, bạn có thể kiểm soát các phụ thuộc nhưng không phải khi các hiệu ứng diễn ra trên các phụ thuộc này.

Tôi tổng hợp ví dụ sau trong mã.

case class IO[+A](val ra:() => A){ 
    def unsafePerformIO() : A = ra(); 
    def map[B](f: A => B) : IO[B] = IO[B](() => f(unsafePerformIO())) 
    def flatMap[B](f: A => IO[B]) : IO[B] = { 
    IO(() => f(ra()).unsafePerformIO()) 
    } 
} 



case class Person(age: Int, name: String) 

object Runner { 

    def getOlderPerson(p1: Person,p2:Person) : Person = 
    if(p1.age > p2.age) 
     p1 
     else 
     p2 

    def printOlder(p1: Person, p2: Person): IO[Unit] = { 
    IO(() => println(getOlderPerson(p1,p2))).map(x => println("Next")) 
    } 

    def printPerson(p:Person) = IO(() => { 
    println(p) 
    p 
    }) 

    def main(args: Array[String]): Unit = { 

    val result = printPerson(Person(31,"Blair")).flatMap(a => printPerson(Person(23,"Tom")) 
            .flatMap(b => printOlder(a,b))) 

    result.unsafePerformIO() 
    } 

} 

Bạn có thể xem hiệu ứng được hoãn lại cho đến khi chính tôi đoán là điều tuyệt vời. Tôi đã nghĩ ra điều này sau khi cảm nhận điều này từ video.

Việc triển khai của tôi có đúng không và sự hiểu biết của tôi có chính xác hay không.

Tôi cũng tự hỏi liệu có nên lấy milage hay không, nó phải được kết hợp với ValidationMonad, như trong ValidationMonad [IO [Person]] để chúng ta có thể đoản mạch khi ngoại lệ xảy ra? Suy nghĩ xin vui lòng.

Blair

Trả lời

27

Nó có giá trị cho chữ ký kiểu của hàm để ghi lại có hay không có tác dụng phụ. Việc thực hiện IO của bạn có giá trị bởi vì nó thực hiện được nhiều điều đó. Nó làm cho mã của bạn được tài liệu tốt hơn; và nếu bạn tái cấu trúc mã của bạn để tách biệt, càng nhiều càng tốt, logic liên quan đến IO từ logic không, bạn đã thực hiện các hàm không liên quan đến IO nhiều composable và dễ kiểm thử hơn. Bạn có thể làm cùng một phép tái cấu trúc mà không có một loại IO rõ ràng; nhưng sử dụng một loại rõ ràng có nghĩa là trình biên dịch có thể giúp bạn thực hiện việc tách.

Nhưng đó mới chỉ là khởi đầu. Trong mã trong câu hỏi của bạn, các hành động IO được mã hóa dưới dạng lambdas và do đó mờ đục; không có gì bạn có thể làm với một hành động IO ngoại trừ chạy nó, và hiệu ứng của nó khi chạy được mã hóa cứng.

Đó không phải là cách duy nhất có thể thực hiện đơn IO.

Ví dụ, tôi có thể làm cho các trường hợp tác vụ IO của tôi mở rộng một đặc điểm chung. Sau đó, tôi có thể, ví dụ, viết một bài kiểm tra chạy một chức năng và xem liệu nó trả về quyền loại của hành động IO.

Trong các trường hợp các lớp đại diện cho các loại hành động IO khác nhau, tôi có thể không bao gồm việc triển khai mã hóa cứng những gì mà các hành động sẽ thực hiện khi tôi chạy. Thay vào đó, tôi có thể decouple rằng bằng cách sử dụng mô hình typeclass. Điều đó sẽ cho phép trao đổi trong các triển khai khác nhau về những hành động của IO. Ví dụ, tôi có thể có một bộ triển khai nói chuyện với một cơ sở dữ liệu sản xuất, và một bộ khác nói chuyện với một cơ sở dữ liệu giả trong bộ nhớ cho mục đích thử nghiệm.

Có cách xử lý tốt các vấn đề này trong Chương 13 ("Hiệu ứng bên ngoài và I/O") của Bjarnason & Sách Chiusano Lập trình chức năng trong Scala. Xem đặc biệt là 13.2.2, “Lợi ích và hạn chế của loại IO đơn giản”.

CẬP NHẬT (tháng 12 năm 2015): "trao đổi trong các triển khai khác nhau về những hành động của IO", những ngày này càng ngày càng nhiều người sử dụng "đơn nguyên tự do" cho loại điều này; xem ví dụ Bài đăng trên blog của John De Goes “A Modern Architecture for FP”.

+0

Cảm ơn. Câu trả lời tuyệt vời tôi sẽ xem xét những ý tưởng đó tối nay. –

+0

Bạn có đoạn mã với các lớp con và nhập các lớp học không? –

+2

Xem các trang trình bày của Runar mà Drexin liên kết đến, cụ thể là công cụ 'ConsoleIO'. Nó thể hiện sự tách biệt khai báo những hành động IO tồn tại ('đối tượng trường hợp GetLine ...', 'trường hợp lớp PutLine ...') từ định nghĩa về những gì có thể xảy ra nếu bạn chạy các hành động đó ('đối tượng ẩn ConsoleEffect .. .'). Nhưng lưu ý có những thứ khác trong đó quá; nó không phải là mã tối thiểu thể hiện _only_ những gì tôi đã nói. –

18

Lợi ích của việc sử dụng đơn nguyên IO là có chương trình thuần túy. Bạn không đẩy các tác dụng phụ lên cao hơn chuỗi, nhưng loại bỏ chúng. Nếu bạn có một chức năng bất tịnh như sau:

def greet { 
    println("What is your name?") 
    val name = readLine 
    println(s"Hello, $name!") 
} 

Bạn có thể loại bỏ các tác dụng phụ bằng cách viết lại nó để:

def greet: IO[Unit] = for { 
    _ <- putStrLn("What is your name?") 
    name <- readLn 
    _ <- putStrLn(s"Hello, $name!") 
} yield() 

Chức năng thứ hai là referentially minh bạch.

Một lời giải thích rất tốt lý do tại sao sử dụng các đơn nguyên IO dẫn đến chương trình thuần túy có thể được tìm thấy in Rúnar Bjarnason's slides từ scala.io (video có thể được tìm thấy here).

+0

Trình lập trình không phải FP tại đây. Tại sao thứ hai tham chiếu trong suốt? Bản in sẽ phụ thuộc vào đầu vào của người dùng được chứa bên trong hàm chào, phải không? – nawfal

+0

@nawfal Nó liên tục minh bạch bởi vì nó không có tác dụng trên thế giới bên ngoài và không phụ thuộc vào trạng thái bên ngoài. Mỗi khi bạn chạy nó, bạn sẽ nhận được cùng một điều: một hành động mà khi chạy sẽ in một số văn bản và yêu cầu đầu vào. Nếu bạn muốn làm điều đó hai lần, bạn có thể gọi hàm hai lần để có hai hành động giống hệt nhau hoặc bạn có thể gọi hàm một lần và sử dụng lại hành động mà nó trả về và chương trình của bạn sẽ giống hệt về mặt ngữ nghĩa. Bạn cũng có thể gọi nó và không bao giờ sử dụng kết quả mà sẽ giống hệt về mặt ngữ nghĩa để không bao giờ gọi hàm đó cả. – puhlen

+0

@puhlen Tôi nhận được điều đó, nhưng sau đó khi bạn gọi đó là hành động trả về, bạn có tác dụng phụ, phải không? Về cơ bản, bạn đang di chuyển một phần có hiệu lực phụ đến một nơi khác? – nawfal

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