Có phải tất cả các ngôn ngữ chức năng sử dụng bộ sưu tập rác?


32

Có một ngôn ngữ chức năng cho phép sử dụng ngữ nghĩa ngăn xếp - hủy xác định tự động ở cuối phạm vi không?


Phá hủy quyết định thực sự chỉ hữu ích với tác dụng phụ. Trong bối cảnh lập trình chức năng thuần túy , điều đó chỉ có nghĩa là đảm bảo rằng một số hành động (đơn điệu) nhất định luôn chạy ở cuối chuỗi. Bên cạnh đó, thật dễ dàng để viết một ngôn ngữ kết hợp chức năng mà không cần bộ sưu tập rác.
Jon Purdy

Tôi quan tâm đến vấn đề của câu hỏi, người ta phải làm gì với otehr?
mattnz

1
Trong một ngôn ngữ chức năng không có bộ sưu tập rác, tôi không thấy cách chia sẻ cấu trúc của các cấu trúc dữ liệu bất biến là có thể. Có thể tạo ra một ngôn ngữ như vậy, nhưng đó không phải là ngôn ngữ tôi sử dụng.
dan_waterworth

Rust có rất nhiều tính năng thường được xác định là 'chức năng' (ít nhất, chúng thường được mong muốn trong các lang không phải là chức năng). Tôi tò mò không biết nó thiếu gì. Mặc định, mặc định, đóng, chuyển func, nạp chồng nguyên tắc, ADT (chưa có GADT), khớp mẫu, tất cả đều không có GC. Còn gì nữa không
Noein

Câu trả lời:


10

Không phải tôi biết, mặc dù tôi không phải là chuyên gia lập trình chức năng.

Về nguyên tắc có vẻ khá khó khăn, bởi vì các giá trị được trả về từ các hàm có thể chứa các tham chiếu đến các giá trị khác được tạo (trên ngăn xếp) trong cùng một hàm hoặc có thể dễ dàng được truyền vào như một tham số hoặc được tham chiếu bởi một cái gì đó được truyền vào như một tham số. Trong C, vấn đề này được giải quyết bằng cách cho phép các con trỏ lơ lửng (hay chính xác hơn là hành vi không xác định) có thể xảy ra nếu lập trình viên không làm mọi thứ đúng. Đó không phải là loại giải pháp mà các nhà thiết kế ngôn ngữ chức năng tán thành.

Có những giải pháp tiềm năng, mặc dù. Một ý tưởng là làm cho tuổi thọ của giá trị trở thành một phần của loại giá trị, cùng với các tham chiếu đến nó và xác định các quy tắc dựa trên loại ngăn chặn các giá trị được cấp phát ngăn xếp được trả về hoặc được tham chiếu bởi thứ gì đó được trả về từ chức năng. Tôi đã không làm việc thông qua các hàm ý, nhưng tôi nghi ngờ nó sẽ là khủng khiếp.

Đối với mã đơn âm, cũng có một giải pháp khác (thực tế hoặc gần như) đơn nguyên, và có thể đưa ra một loại IORef tự động bị phá hủy xác định. Nguyên tắc là xác định các hành động "lồng". Khi được kết hợp (sử dụng toán tử kết hợp), các toán tử này xác định luồng điều khiển lồng nhau - tôi nghĩ "phần tử XML", với hầu hết các giá trị cung cấp cặp thẻ bắt đầu và kết thúc bên ngoài. Các "thẻ XML" này chỉ xác định thứ tự các hành động đơn điệu ở một mức độ trừu tượng khác.

Tại một số điểm (ở phía bên phải của chuỗi thành phần kết hợp), bạn cần một số loại đầu cuối để kết thúc việc làm tổ - một cái gì đó để lấp đầy lỗ hổng ở giữa. Nhu cầu về một bộ kết thúc là điều có lẽ có nghĩa là toán tử thành phần lồng nhau không đơn điệu, mặc dù vậy, tôi không hoàn toàn chắc chắn vì tôi đã không làm việc thông qua các chi tiết. Vì tất cả việc áp dụng terminator thực hiện là chuyển đổi một hành động lồng nhau thành một hành động đơn điệu bình thường có hiệu quả, có thể không - nó không nhất thiết ảnh hưởng đến toán tử thành phần lồng.

Nhiều hành động đặc biệt này sẽ có bước "thẻ kết thúc" null và sẽ đánh đồng bước "thẻ bắt đầu" với một số hành động đơn điệu đơn giản. Nhưng một số sẽ đại diện cho khai báo biến. Chúng sẽ đại diện cho hàm tạo với thẻ bắt đầu và hàm hủy với thẻ kết thúc. Vì vậy, bạn nhận được một cái gì đó như ...

act = terminate ((def-var "hello" ) >>>= \h ->
                 (def-var " world") >>>= \w ->
                 (use-val ((get h) ++ (get w)))
                )

Dịch sang một tác phẩm đơn âm theo thứ tự thực hiện sau, mỗi thẻ (không phải phần tử) trở thành một hành động đơn âm bình thường ...

<def-var val="hello">  --  construction
  <def-var val=" world>  --  construction
    <use-val ...>
      <terminator/>
    </use-val>  --  do nothing
  </def-val>  --  destruction
</def-val>  --  destruction

Các quy tắc như thế này có thể cho phép thực hiện RAII theo kiểu C ++. Các tham chiếu giống IORef không thể thoát khỏi phạm vi của chúng, vì những lý do tương tự như lý do tại sao các IORef bình thường không thể thoát khỏi đơn nguyên - các quy tắc của thành phần kết hợp không cung cấp cách nào để tham chiếu thoát ra.

EDIT - Tôi gần như quên nói - có một khu vực nhất định tôi không chắc chắn về đây. Về cơ bản, điều quan trọng là phải đảm bảo rằng một biến bên ngoài không thể tham chiếu một biến bên trong, do đó, phải có những hạn chế đối với những gì bạn có thể làm với các tham chiếu giống IORef này. Một lần nữa, tôi đã không làm việc thông qua tất cả các chi tiết.

Do đó, việc xây dựng có thể mở một tập tin phá hủy. Xây dựng có thể mở một ổ cắm mà phá hủy đóng cửa. Về cơ bản, như trong C ++, các biến trở thành người quản lý tài nguyên. Nhưng không giống như C ++, không có đối tượng phân bổ heap không thể tự động bị hủy.

Mặc dù cấu trúc này hỗ trợ RAII, bạn vẫn cần một trình thu gom rác. Mặc dù một hành động lồng nhau có thể phân bổ và giải phóng bộ nhớ, coi nó như một tài nguyên, vẫn có tất cả các tham chiếu đến các giá trị chức năng (có khả năng được chia sẻ) trong khối bộ nhớ đó và các nơi khác. Cho rằng bộ nhớ có thể được phân bổ đơn giản trên ngăn xếp, tránh sự cần thiết của một đống miễn phí, ý nghĩa thực sự (nếu có) là đối với các loại quản lý tài nguyên khác.

Vì vậy, điều này đạt được là tách biệt quản lý tài nguyên theo kiểu RAII khỏi quản lý bộ nhớ, ít nhất là trong trường hợp RAII dựa trên phạm vi lồng đơn giản. Bạn vẫn cần một trình thu gom rác để quản lý bộ nhớ, nhưng bạn có thể dọn sạch các tài nguyên khác một cách an toàn và kịp thời.


Tôi không thấy lý do tại sao một GC là cần thiết trong tất cả các ngôn ngữ chức năng. nếu bạn có một khung RAII kiểu C ++, trình biên dịch cũng có thể sử dụng cơ chế đó. Các giá trị được chia sẻ không có vấn đề gì đối với các khung RAII (xem C ++ shared_ptr<>), bạn vẫn tiếp tục phá hủy xác định. Một điều khó khăn cho RAII là các tài liệu tham khảo theo chu kỳ; RAII hoạt động sạch sẽ nếu đồ thị quyền sở hữu là đồ thị theo chu kỳ có hướng.
MSalters

Vấn đề là, phong cách lập trình chức năng thực tế được xây dựng xung quanh các bao đóng / lambdas / các hàm ẩn danh. Không có GC, bạn không có quyền tự do sử dụng các bao đóng của mình, vì vậy ngôn ngữ của bạn trở nên ít chức năng hơn đáng kể.
sắp tới

@asingstorm - C ++ có lambdas (kể từ C ++ 11) nhưng không có trình thu gom rác tiêu chuẩn. Các lambdas cũng mang môi trường của chúng trong một bao đóng - và các yếu tố trong môi trường đó có thể được chuyển qua tham chiếu, cũng như khả năng con trỏ được truyền theo giá trị. Nhưng như tôi đã viết trong đoạn thứ hai của mình, C ++ cho phép khả năng treo con trỏ - đó là trách nhiệm của các lập trình viên (chứ không phải là trình biên dịch hoặc môi trường thời gian chạy) để đảm bảo điều đó không bao giờ xảy ra.
Steve314

@MSalters - có các chi phí liên quan đến việc đảm bảo rằng không có chu trình tham chiếu nào có thể được tạo. Do đó, ít nhất là không tầm thường để khiến ngôn ngữ chịu trách nhiệm cho hạn chế đó. Việc gán cho một con trỏ có thể trở thành một hoạt động không liên tục. Mặc dù nó vẫn có thể là lựa chọn tốt nhất trong một số trường hợp. Thu gom rác tránh vấn đề đó, với các chi phí khác nhau. Làm cho lập trình viên có trách nhiệm là một việc khác. Không có lý do mạnh mẽ tại sao con trỏ lơ lửng nên ổn trong các ngôn ngữ bắt buộc nhưng không phải chức năng, nhưng tôi vẫn không khuyên bạn nên viết con trỏ-dangling-Haskell.
Steve314

Tôi cho rằng việc quản lý bộ nhớ thủ công có nghĩa là bạn không có quyền tự do sử dụng các lần đóng C ++ 11 giống như khi bạn đóng Lisp hoặc Haskell. (Tôi thực sự khá thích thú với việc tìm hiểu chi tiết về sự đánh đổi này, vì tôi muốn viết một ngôn ngữ lập trình hệ thống chức năng ...)
sắp diễn ra vào

3

Nếu bạn coi C ++ là ngôn ngữ chức năng (nó có lambdas), thì đó là một ví dụ về ngôn ngữ không sử dụng bộ sưu tập rác.


8
Điều gì sẽ xảy ra nếu bạn không coi C ++ là ngôn ngữ chức năng (IMHO không phải vậy, mặc dù bạn có thể viết một chương trình chức năng với nó, bạn cũng có thể viết một số chương trình cực kỳ phi chức năng (chức năng ....) với nó)
mattnz

@mattnz Sau đó, tôi đoán câu trả lời không áp dụng. Tôi không chắc điều gì xảy ra trong các ngôn ngữ khác (ví dụ như haskel)
BЈовић

9
Nói C ++ là chức năng cũng giống như nói rằng Perl là hướng đối tượng ...
Dynamic

Ít nhất trình biên dịch c ++ có thể kiểm tra tác dụng phụ. (thông qua const)
tp1

@ tp1 - (1) Tôi hy vọng điều này sẽ không thụt lùi vào ngôn ngữ của ai là tốt nhất và (2) điều đó không thực sự đúng. Đầu tiên, các hiệu ứng thực sự quan trọng chủ yếu là I / O. Thứ hai, ngay cả đối với các hiệu ứng trên bộ nhớ có thể thay đổi, const không chặn chúng. Ngay cả khi bạn cho rằng không có khả năng lật đổ hệ thống kiểu (nói chung là hợp lý trong C ++), vẫn có vấn đề về tính hợp lý logic và từ khóa C ++ "có thể thay đổi". Về cơ bản, bạn vẫn có thể có đột biến mặc dù const. Bạn sẽ đảm bảo rằng kết quả vẫn "hợp lý" như nhau, nhưng không nhất thiết phải giống như vậy.
Steve314

2

Tôi phải nói rằng câu hỏi hơi khó hiểu vì nó giả định rằng có một bộ sưu tập "ngôn ngữ chức năng" tiêu chuẩn. Hầu như mọi ngôn ngữ lập trình đều hỗ trợ một số lượng lập trình chức năng. Và, hầu như mọi ngôn ngữ lập trình đều hỗ trợ một số lượng lập trình bắt buộc. Trường hợp nào người ta vẽ đường để nói đó là ngôn ngữ chức năng và ngôn ngữ bắt buộc, ngoài hướng dẫn bởi định kiến ​​văn hóa và giáo điều phổ biến?

Một cách tốt hơn để diễn đạt câu hỏi sẽ là "có thể hỗ trợ lập trình chức năng trong một bộ nhớ được cấp phát ngăn xếp". Câu trả lời là, như đã đề cập, rất khó. Phong cách lập trình chức năng thúc đẩy việc phân bổ các cấu trúc dữ liệu đệ quy theo ý muốn, đòi hỏi một bộ nhớ heap (cho dù rác được thu thập hoặc tham chiếu được tính). Tuy nhiên, có một kỹ thuật phân tích trình biên dịch khá phức tạp được gọi là phân tích bộ nhớ dựa trên khu vực , trong đó trình biên dịch có thể chia heap thành các khối lớn có thể được phân bổ và phân bổ tự động, theo cách tương tự như phân bổ ngăn xếp. Trang Wikipedia liệt kê các triển khai khác nhau của kỹ thuật, cho cả hai ngôn ngữ "chức năng" và "mệnh lệnh".


1
Đếm tham chiếu IMO bộ sưu tập rác và việc có một đống không có nghĩa là đó là những lựa chọn duy nhất. C mallocs và mfrees sử dụng một đống, nhưng không có trình thu gom rác (tiêu chuẩn) và chỉ có số tham chiếu nếu bạn viết mã để làm điều đó. C ++ gần như giống nhau - nó có (như tiêu chuẩn, trong C ++ 11) con trỏ thông minh được tích hợp tính đếm tham chiếu, nhưng bạn vẫn có thể làm thủ công mới và xóa nếu bạn thực sự cần.
Steve314

Một lý do phổ biến để tuyên bố rằng việc đếm tham chiếu không phải là thu gom rác là vì nó không thu thập được chu kỳ tham chiếu. Điều đó chắc chắn áp dụng cho các triển khai đơn giản (có thể bao gồm cả con trỏ thông minh C ++ - tôi chưa kiểm tra) nhưng không phải lúc nào cũng như vậy. Ít nhất một máy ảo Java (của IBM, IIRC) đã sử dụng tính tham chiếu làm cơ sở cho bộ sưu tập rác của nó.
Steve314
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.