2011-01-25 33 views
11

Tôi biết rằng Scala hỗ trợ gọi theo tên từ ALGOL, và tôi nghĩ rằng tôi hiểu điều đó có nghĩa là gì, nhưng Scala có thể gọi tham chiếu bằng C#, VB.NET và C++ không? Tôi biết rằng Java không thể thực hiện gọi theo tham chiếu, nhưng tôi không chắc liệu giới hạn này có phải là do ngôn ngữ hay cũng là JVM.Có thể gọi Scala bằng cách tham chiếu không?

Điều này sẽ hữu ích khi bạn muốn chuyển một cấu trúc dữ liệu khổng lồ đến một phương pháp, nhưng bạn không muốn tạo một bản sao của nó. Gọi theo tham chiếu có vẻ hoàn hảo trong trường hợp này.

+1

Tôi không nghĩ rằng bạn có thể chuyển cấu trúc dữ liệu theo giá trị trong Scala. – Gabe

+0

@Gabe Nó vẫn là [pass/call-by-value] (http://en.wikipedia.org/wiki/Evaluation_strategy) (tham chiếu-by-value-of-the-based-object-reference cho các kiểu AnyRef và Người dùng Python/Java thích lạm dụng thuật ngữ "pass-by-reference" cho điều này ;-). Tuy nhiên, pass/call-by-reference có nghĩa là thiết lập biến * trong hàm * sẽ thiết lập giá trị của biến được truyền vào (ví dụ '& ref' trong C++ hoặc' ByRef' trong VB hoặc 'out/ref' trong C#) . Điều này có thể được mô phỏng * với các đối tượng được truyền và đột biến trạng thái nhưng nó không giống nhau (C có thể mô phỏng nó bằng cách sửa đổi một giá trị được trỏ tới bởi một con trỏ). –

+1

@pst: Tôi đang đề cập đến phần "truyền một cấu trúc dữ liệu khổng lồ đến một phương thức". Hoặc là Scala có một cách mà tôi không biết về việc sao chép cấu trúc dữ liệu khi truyền chúng sang một hàm, hoặc các cấu trúc dữ liệu đã được chuyển qua tham chiếu và trích dẫn từ OP không liên quan. – Gabe

Trả lời

34

Cả Java và Scala đều sử dụng gọi theo giá trị độc quyền, ngoại trừ giá trị là một số nguyên thủy hoặc một con trỏ đến một đối tượng. Nếu đối tượng của bạn chứa các trường có thể thay đổi, thì có rất ít sự khác biệt đáng kể giữa điều này và gọi bằng tham chiếu.

Vì bạn luôn chuyển các con trỏ cho các đối tượng không phải là đối tượng, bạn không gặp phải vấn đề phải sao chép nhiều lần đối tượng khổng lồ.

Ngẫu nhiên, cuộc gọi của Scala theo tên được thực hiện bằng cách sử dụng gọi theo giá trị, với giá trị là đối tượng hàm (con trỏ đến a) trả về kết quả của biểu thức.

+0

Bạn có thể giải thích rõ hơn về các trường có thể thay đổi có hiệu lực không? – royco

+1

Vâng, giả sử bạn muốn biến một biến 'x'. Bạn có thể chuyển nó trong C dưới dạng 'int * x'. Trong Scala, bạn sẽ có một lớp chứa biến đó: 'class X (var x: Int)'. Bây giờ nếu bạn chuyển lớp đó sang một phương thức nào đó, nó có thể thay đổi giá trị của 'x'. –

+11

Nói rằng Scala sử dụng gọi theo giá trị độc quyền là khó hiểu ngôn ngữ với việc thực hiện nó. Nếu chúng ta đi đến hội đồng, thì tất cả các ngôn ngữ đều gọi theo giá trị độc quyền. Scala _could_ hỗ trợ gọi theo tham chiếu trên JVM, đơn giản như việc tạo một đối tượng container để chuyển tham chiếu hoặc nguyên thủy, và sau đó đọc lại từ vùng chứa đó khi cuộc gọi trả về. –

0

Đối với ngôn ngữ có "mọi thứ là đối tượng" và không thể truy cập tham chiếu đối tượng, ví dụ: Java và Scala, khi đó mọi tham số hàm là một tham chiếu được truyền theo giá trị ở một mức trừu tượng nào đó bên dưới ngôn ngữ. Tuy nhiên, từ quan điểm của ngữ nghĩa trừu tượng ngôn ngữ, có một lời gọi theo tham chiếu hoặc giá trị gọi theo giá trị, tùy thuộc vào việc hàm có được cung cấp một bản sao của đối tượng được tham chiếu hay không. Trong trường hợp này, thuật ngữ chia sẻ cuộc gọi bao gồm cả lời gọi tham chiếu và giá trị gọi theo giá trị ở cấp độ ngôn ngữ trừu tượng. Do đó, có thể nói Java là call-by-value ở mức trừu tượng bên dưới ngữ nghĩa ngôn ngữ (nghĩa là so sánh với cách nó được dịch giả thuyết sang C hoặc bytecode cho máy ảo), trong khi cũng nói rằng Java và Scala là (ngoại trừ các loại nội trang) gọi theo tham chiếu tại ngữ nghĩa của "tất cả mọi thứ là một đối tượng" trừu tượng. Trong Java và Scala, một số kiểu nội trang (a/k/nguyên thủy) nhất định được truyền theo giá trị (ví dụ int hoặc Int), và mọi kiểu do người dùng định nghĩa đều được truyền qua tham chiếu (tức là phải sao chép theo cách thủ công). họ chỉ chuyển giá trị của họ).

Lưu ý tôi đã cập nhật số Call-by-sharing section của Wikipedia để làm cho điều này rõ ràng hơn.

Có lẽ Wikipedia bị nhầm lẫn về sự khác biệt giữa giá trị truyền qua và giá trị gọi theo giá trị? Tôi nghĩ rằng pass-by-value là thuật ngữ tổng quát hơn, vì nó áp dụng cho các biểu thức gán, cũng như ứng dụng hàm. Tôi không bận tâm cố gắng sửa lỗi đó ở Wikipedia, để nó cho người khác bẻ khóa.

Không có sự khác biệt ở cấp độ ngữ nghĩa trong đó "mọi thứ là một đối tượng" giữa gọi theo tham chiếu và giá trị theo từng cuộc gọi, khi đối tượng không thay đổi. Do đó, một ngôn ngữ cho phép khai báo giá trị gọi theo giá trị so với gọi theo tham chiếu (chẳng hạn như ngôn ngữ giống như Scala mà tôi đang phát triển), có thể được tối ưu hóa bằng cách trì hoãn sao chép theo giá trị cho đến khi đối tượng được sửa đổi.


Những người bỏ phiếu này dường như không hiểu "chia sẻ cuộc gọi" là gì.

Dưới đây tôi sẽ thêm ghi lên tôi đã làm cho ngôn ngữ Copute của tôi (nhắm mục tiêu JVM), nơi tôi thảo luận về chiến lược đánh giá.


Ngay cả với độ tinh khiết, không có ngôn ngữ hoàn chỉnh Turing (tức làcho phép đệ quy) là hoàn toàn khai báo, bởi vì nó phải chọn một chiến lược đánh giá. Chiến lược đánh giá là thứ tự đánh giá thời gian chạy tương đối giữa các hàm và đối số của chúng. Chiến lược đánh giá chức năng có thể nghiêm ngặt hoặc không nghiêm ngặt, tương tự như mong muốn hoặc lười biếng tương ứng, bởi vì tất cả các biểu thức đều có chức năng. Eager nghĩa là các biểu thức đối số được đánh giá trước hàm của chúng; trong khi đó, lười biếng có nghĩa là các biểu thức đối số chỉ được đánh giá (một lần) tại thời điểm chạy của lần sử dụng đầu tiên của chúng trong hàm. Chiến lược đánh giá xác định hiệu suất, xác định, gỡ lỗi và cân bằng ngữ nghĩa hoạt động. Đối với các chương trình thuần túy, nó không làm thay đổi kết quả ngữ nghĩa denotational, bởi vì với độ tinh khiết, các tác dụng phụ bắt buộc của thứ tự đánh giá chỉ gây ra sự không xác định trong (nghĩa là giới hạn về mặt), tiêu thụ bộ nhớ, thời gian thực hiện, độ trễ và các miền không kết thúc .

Về cơ bản tất cả các biểu thức là hàm (thành phần), tức là các hàm là các hàm thuần túy không có đầu vào, các toán tử đơn nhất là các hàm thuần túy với một đầu vào, toán tử nhị phân là các hàm thuần túy với hai đầu vào, hàm tạo. nếu, cho, trong khi) có thể được mô hình hoá với các hàm. Thứ tự mà chúng tôi đánh giá các hàm này không được xác định bởi cú pháp, ví dụ: f (g()) có thể háo hức đánh giá g sau đó f trên kết quả của g hoặc nó có thể đánh giá f và chỉ lazily đánh giá g khi kết quả của nó là cần thiết trong f.

Trường hợp cũ (háo hức) là giá trị gọi theo giá trị (CBV) và sau (lười) là cuộc gọi theo từng tên (CBN). CBV có một biến thể gọi-by-chia sẻ, phổ biến trong các ngôn ngữ OOP hiện đại như Java, Python, Ruby, vv, trong đó hàm impure ngầm nhập vào một số đối tượng có thể thay đổi bằng cách tham chiếu. CBN có một biến thể gọi theo nhu cầu (cũng là CBN), trong đó các đối số hàm chỉ được đánh giá một lần (không giống như các hàm ghi nhớ). Gọi theo nhu cầu gần như luôn được sử dụng thay vì gọi theo tên, bởi vì nó nhanh hơn theo cấp số nhân. Thông thường cả hai biến thể của CBN chỉ xuất hiện với độ tinh khiết, bởi vì sự bất hòa giữa hệ thống phân cấp hàm được khai báo và thứ tự thời gian chạy của đánh giá.

Ngôn ngữ thường có chiến lược đánh giá mặc định và một số có cú pháp tùy ý buộc một hàm được đánh giá theo mặc định. Các ngôn ngữ háo hức theo mặc định, thường đánh giá kết nối boolean (a/k/a "và", & &) và ngắt kết nối (a/k/a "hoặc", ||), bởi vì toán hạng thứ hai không phải là cần thiết trong một nửa số trường hợp, tức là đúng || bất kỳ điều gì == đúng và sai & & mọi thứ == sai.

+0

Đây là một tham chiếu: http://lambda-the-ultimate.org/node/4180#comment-64168 –

0

Dưới đây là cách mô phỏng tham số tham chiếu trong Scala.

def someFunc(var_par_x : Function[Int,Unit]) { 
    var_par_x(42) // set the reference parameter to 42 
} 

var y = 0 
someFunc((x => y=x)) 
println(y) 

Vâng, được rồi, không chính xác những gì người lập trình Pascal hoặc C++ được sử dụng; nhưng sau đó, rất ít ở Scala. Điều ngược lại là điều này cho phép người gọi linh hoạt hơn với những gì họ có thể làm với giá trị được gửi đến tham số. Ví dụ.

someFunc((x => println(x))) 
Các vấn đề liên quan