2013-01-13 27 views
76

Tôi nhận thấy rằng có một số câu hỏi ở đây về những gì chức năng currying và một phần được áp dụng, nhưng tôi hỏi về cách chúng khác nhau. Như một ví dụ đơn giản, đây là một chức năng cà ri cho việc tìm kiếm số chẵn:Scala currying vs các hàm được áp dụng một phần

def filter(xs: List[Int], p: Int => Boolean): List[Int] = 
    if (xs.isEmpty) xs 
    else if (p(xs.head)) xs.head :: filter(xs.tail, p) 
    else filter(xs.tail, p) 

def modN(n: Int)(x: Int) = ((x % n) == 0) 

Vì vậy, bạn có thể viết như sau để sử dụng này:

val nums = List(1,2,3,4,5,6,7,8) 
println(filter(nums, modN(2)) 

trả về: List(2,4,6,8). Nhưng tôi thấy rằng tôi có thể làm điều tương tự như thế này:

def modN(n: Int, x: Int) = ((x % n) == 0) 

val p = modN(2, _: Int) 
println(filter(nums, p)) 

mà cũng trả về: List(2,4,6,8).

Vì vậy, câu hỏi của tôi là, điểm khác biệt chính giữa hai yếu tố này là gì và khi nào bạn sẽ sử dụng cái kia? Đây có phải là quá đơn giản của một ví dụ để cho thấy lý do tại sao người ta sẽ được sử dụng trên khác?

+0

Khi được áp dụng một phần, chi phí đại diện cho phiên bản đã được curried và không curried trong bộ nhớ * có thể khác nhau, do đó hiệu suất thời gian chạy cũng có thể bị ảnh hưởng. (Đó là, nếu trình tối ưu hóa không đủ thông minh để chọn biểu diễn tối ưu trong cả hai trường hợp.) Tôi không đủ quen thuộc với Scala để nói sự khác biệt chính xác là gì. –

+1

Hãy xem phần này: http://stackoverflow.com/questions/8063325/usefulness-as-in-practical-applications-of-currying-vs-partial-application-i – Utaal

+0

Tôi thấy giải thích này rất hữu ích, anh giải thích về các chức năng từng phần, chức năng được áp dụng một phần và currying, tất cả trong một bài đăng: http: //stackoverflow.com/a/8650639/1287554 –

Trả lời

83

Sự khác biệt ngữ nghĩa đã được giải thích khá rõ trong the answer linked to by Plasty Grove.

Về mặt chức năng, có vẻ như không có sự khác biệt nhiều. Hãy xem xét một số ví dụ để xác minh điều đó. Đầu tiên, một chức năng bình thường:

scala> def modN(n: Int, x: Int) = ((x % n) == 0) 
scala> modN(5, _ : Int) 
res0: Int => Boolean = <function1> 

Vì vậy, chúng ta có được một phần áp dụng <function1> mà phải mất một Int, bởi vì chúng tôi đã đưa nó trở thành số nguyên đầu tiên. Càng xa càng tốt. Bây giờ để tách lạng bộ:

scala> def modNCurried(n: Int)(x: Int) = ((x % n) == 0) 

Với ký hiệu này, bạn muốn ngây thơ mong đợi sau đây để làm việc:

scala> modNCurried(5) 
<console>:9: error: missing arguments for method modN; 
follow this method with `_' if you want to treat it as a partially applied function 
      modNCurried(5) 

Vì vậy, các nhiều tham số danh sách ký hiệu không thực sự dường như tạo một hàm cà ri ngay lập tức (giả sử tránh chi phí không cần thiết) nhưng chờ bạn nói rõ ràng rằng bạn muốn nó được thu lại (ký hiệu có một số số other advantages):

scala> modNCurried(5) _ 
res24: Int => Boolean = <function1> 

Đó là chính xác cùng một điều chúng tôi đã nhận trước đây, do đó, không có sự khác biệt ở đây, ngoại trừ ký hiệu. Một ví dụ khác:

scala> modN _ 
res35: (Int, Int) => Boolean = <function2> 

scala> modNCurried _ 
res36: Int => (Int => Boolean) = <function1> 

này mô tả cách một phần áp dụng một "bình thường" Kết quả chức năng trong một hàm mang theo tất cả các thông số, trong khi đó áp dụng một phần chức năng với nhiều danh sách tham số tạo ra một chuỗi các chức năng, một cho mỗi danh sách tham số đó, tất cả trở lại một chức năng mới:

scala> def foo(a:Int, b:Int)(x:Int)(y:Int) = a * b + x - y 
scala> foo _ 
res42: (Int, Int) => Int => (Int => Int) = <function2> 

scala> res42(5) 
<console>:10: error: not enough arguments for method apply: (v1: Int, v2: Int)Int => (Int => Int) in trait Function2. 
Unspecified value parameter v2. 

Như bạn có thể thấy, bởi vì danh sách tham số đầu tiên của foo có hai thông số, chức năng đầu tiên trong chuỗi cà ri có hai tham số.


Tóm lại, các chức năng được áp dụng một phần không thực sự là các chức năng được định dạng khác nhau về chức năng.Này được dễ dàng xác minh được rằng bạn có thể chuyển đổi bất kỳ chức năng để một cà ri một:

scala> (modN _).curried 
res45: Int => (Int => Boolean) = <function1 

scala> modNCurried _ 
res46: Int => (Int => Boolean) = <function1> 

bài Scriptum

Lưu ý: Lý do mà ví dụ của bạn println(filter(nums, modN(2)) công trình mà không có dấu gạch dưới sau modN(2) dường như được rằng trình biên dịch Scala chỉ đơn giản giả định rằng gạch dưới là một tiện lợi cho lập trình viên.


Addition: Như @asflierl đã chỉ đúng ra, Scala dường như không có khả năng suy luận kiểu khi áp dụng một phần "bình thường" chức năng:

scala> modN(5, _) 
<console>:9: error: missing parameter type for expanded function ((x$1) => modN(5, x$1)) 

Trong khi thông tin đó là có sẵn cho các hàm được viết bằng cách sử dụng nhiều ký hiệu danh sách tham số:

scala> modNCurried(5) _ 
res3: Int => Boolean = <function1> 

This answers cho biết cách này có thể rất hữu ích.

+0

một sự khác biệt quan trọng là suy luận kiểu hoạt động khác nhau: https://gist.github.com/4529020 –

+0

Cảm ơn, tôi đã thêm ghi chú về nhận xét của bạn :) – fresskoma

17

Currying phải làm với bộ dữ liệu: chuyển một hàm lấy đối số tuple thành một đối số có n đối số riêng biệt và ngược lại. Nhớ rằng đây là chìa khóa để phân biệt cà ri và một phần ứng dụng, ngay cả trong các ngôn ngữ không hỗ trợ làm sạch cà ri một cách rõ ràng.

curry :: ((a, b) -> c) -> a -> b -> c 
    -- curry converts a function that takes all args in a tuple 
    -- into one that takes separate arguments 

uncurry :: (a -> b -> c) -> (a, b) -> c 
    -- uncurry converts a function of separate args into a function on pairs. 

ứng dụng một phần là khả năng áp dụng một chức năng để một số đối số, năng suất một chức năng mới cho các đối số còn lại.

Thật dễ dàng để nhớ nếu bạn chỉ nghĩ rằng cà ri là sự chuyển đổi để làm với bộ dữ liệu.

Trong ngôn ngữ được curried theo mặc định (chẳng hạn như Haskell) sự khác biệt là rõ ràng - bạn phải thực sự làm một cái gì đó để vượt qua đối số trong một bộ. Nhưng hầu hết các ngôn ngữ khác, bao gồm cả Scala, không được bảo đảm theo mặc định - tất cả các arg được chuyển thành bộ dữ liệu, do đó, curry/uncurry ít hữu ích hơn và ít rõ ràng hơn. Và mọi người thậm chí còn nghĩ rằng ứng dụng một phần và currying là những điều tương tự - chỉ vì họ không thể đại diện cho các chức năng curried một cách dễ dàng!

+0

Tôi hoàn toàn đồng ý. Trong thuật ngữ Scala "currying" theo nghĩa gốc của từ này là "quá trình chuyển đổi" một hàm với một danh sách tham số thành một hàm có nhiều danh sách tham số. Trong Scala, phép chuyển đổi này có thể được thực hiện bằng cách sử dụng ".curried". Thật không may, Scala dường như đã quá tải ý nghĩa của từ này một chút vì ban đầu nó sẽ được gọi là ".curry" thay vì ".curried". –

2

chức năng đa biến:

def modN(n: Int, x: Int) = ((x % n) == 0) 

tách lạng bộ (hoặc chức năng cà ri):

def modNCurried(n: Int)(x: Int) = ((x % n) == 0) 

chức năng Vì vậy, nó không phải áp dụng một phần đó là so sánh với tách lạng bộ. Đó là chức năng đa biến. Những gì có thể so sánh với hàm được áp dụng một phần là kết quả gọi hàm của hàm được kết xuất, đó là hàm có cùng danh sách tham số mà hàm được áp dụng một phần.

0

Chỉ cần làm rõ về điểm cuối cùng

Addition: Như @asflierl đã chỉ đúng ra, Scala dường như không để có thể suy ra các loại khi áp dụng một phần "bình thường" chức năng:

Scala có thể suy ra các loại nếu tất cả tham số là ký tự đại diện nhưng không phải khi một số tham số được chỉ định và một số trong số đó không được chỉ định.

scala> modN(_,_) 
res38: (Int, Int) => Boolean = <function2> 

scala> modN(1,_) 
<console>:13: error: missing parameter type for expanded function ((x$1) => modN(1, x$1)) 
     modN(1,_) 
      ^
Các vấn đề liên quan