Điều gì có thể đi sai trong bối cảnh lập trình chức năng nếu đối tượng của tôi có thể thay đổi?


9

Tôi có thể thấy những lợi ích của các đối tượng có thể thay đổi và bất biến như các đối tượng bất biến lấy đi rất nhiều khó khăn để khắc phục các sự cố trong lập trình đa luồng do trạng thái chia sẻ và có thể ghi. Ngược lại, các đối tượng có thể thay đổi giúp xử lý danh tính của đối tượng thay vì tạo bản sao mới mỗi lần và do đó cũng cải thiện hiệu suất và việc sử dụng bộ nhớ, đặc biệt đối với các đối tượng lớn hơn.

Một điều tôi đang cố gắng hiểu là những gì có thể sai trong việc có các đối tượng có thể thay đổi trong bối cảnh lập trình chức năng. Giống như một trong những điểm được nói với tôi là kết quả của các hàm gọi theo thứ tự khác nhau không mang tính quyết định.

Tôi đang tìm kiếm một ví dụ cụ thể thực sự trong đó rất rõ ràng những gì có thể sai khi sử dụng đối tượng có thể thay đổi trong lập trình hàm. Về cơ bản nếu nó là xấu, nó là xấu bất kể OO hoặc mô hình lập trình chức năng, phải không?

Tôi tin rằng bên dưới tuyên bố của riêng tôi trả lời câu hỏi này. Nhưng tôi vẫn cần một số ví dụ để tôi có thể cảm thấy nó tự nhiên hơn.

OO giúp quản lý sự phụ thuộc và viết chương trình dễ dàng và duy trì hơn với sự trợ giúp của các công cụ như đóng gói, đa hình, v.v.

Lập trình hàm cũng có động cơ thúc đẩy mã duy trì nhưng bằng cách sử dụng kiểu giúp loại bỏ nhu cầu sử dụng các công cụ và kỹ thuật OO - một trong số đó tôi tin là bằng cách giảm thiểu tác dụng phụ, chức năng thuần túy, v.v.


1
@Ruben tôi muốn nói rằng hầu hết các ngôn ngữ chức năng đều cho phép biến có thể thay đổi, nhưng làm cho nó khác đi khi sử dụng chúng, ví dụ: các biến có thể thay đổi có một loại khác
jk.

1
Tôi nghĩ rằng bạn có thể đã trộn lẫn bất biến và đột biến trong đoạn đầu tiên của bạn?
jk.

1
@jk., anh chắc chắn đã làm. Chỉnh sửa để sửa nó.
David Arno

6
@Ruben Lập trình chức năng là một mô hình. Vì vậy, nó không yêu cầu một ngôn ngữ lập trình chức năng. Và một số ngôn ngữ fp như F # có tính năng này .
Christophe

1
@Ruben không đặc biệt tôi đã nghĩ đến việc Mvars trong Haskell hackage.haskell.org/package/base-4.9.1.0/docs/... ngôn ngữ khác nhau đã giải pháp khác nhau của khóa học hoặc IORefs hackage.haskell.org/package/base-4.11.1.0 /docs/Data-IORef.html mặc dù tất nhiên bạn sẽ sử dụng cả hai từ trong các đơn vị
jk.

Câu trả lời:


7

Tôi nghĩ tầm quan trọng được thể hiện rõ nhất bằng cách so sánh với cách tiếp cận OO

ví dụ: giả sử chúng ta có một đối tượng

Order
{
    string Status {get;set;}
    Purchase()
    {
        this.Status = "Purchased";
    }
}

Trong mô hình OO, phương thức được gắn vào dữ liệu và điều đó có ý nghĩa đối với dữ liệu đó bị đột biến bởi phương thức.

var order = new Order();
order.Purchase();
Console.WriteLine(order.Status); // "Purchased"

Trong mô hình chức năng, chúng tôi xác định một kết quả về mặt chức năng. một đơn đặt hàng đã mua kết quả của chức năng mua hàng được áp dụng cho một đơn đặt hàng. Điều này ngụ ý một vài điều mà chúng ta cần phải chắc chắn về

var order = new Order(); //this is a 'new order'
var purchasedOrder = purchase(order); // this is a 'purchased order'
Console.WriteLine(order.Status); // "New" order is still a 'new order'

Bạn có mong đợi order.Status == "Đã mua" không?

Nó cũng ngụ ý rằng các chức năng của chúng tôi là idempotent. I E. chạy chúng hai lần sẽ tạo ra kết quả giống nhau mỗi lần.

var order = new Order(); //new order
var purchasedOrder = purchase(order); //purchased order
var purchasedOrder2 = purchase(order); //another purchased order
var purchasedOrder = purchase(purchasedOrder); //error! cant purchase an order twice

Nếu đơn đặt hàng đã bị thay đổi bởi chức năng mua hàng, buyOrder2 sẽ thất bại.

Bằng cách định nghĩa mọi thứ là kết quả của các hàm, nó cho phép chúng ta sử dụng các kết quả đó mà không thực sự tính toán chúng. Mà trong điều khoản lập trình được thực hiện hoãn lại.

Điều này có thể hữu ích cho chính nó, nhưng một khi chúng ta không chắc chắn khi nào một chức năng sẽ thực sự xảy ra VÀ chúng ta ổn về điều đó, chúng ta có thể tận dụng xử lý song song nhiều hơn so với mô hình OO.

Chúng tôi biết rằng việc chạy một chức năng sẽ không ảnh hưởng đến kết quả của chức năng khác; vì vậy chúng ta có thể rời khỏi máy tính để thực thi chúng theo bất kỳ thứ tự nào nó chọn, sử dụng bao nhiêu chủ đề tùy thích.

Nếu một hàm làm thay đổi đầu vào của nó, chúng ta phải cẩn thận hơn nhiều về những thứ đó.


cảm ơn !! rất hữu ích Vì vậy, việc triển khai mua hàng mới sẽ giống như Order Purchase() { return new Order(Status = "Purchased") } vậy để trạng thái chỉ được đọc trường. ? Một lần nữa tại sao thực hành này có liên quan nhiều hơn trong bối cảnh của mô hình lập trình chức năng? Lợi ích bạn đề cập cũng có thể được nhìn thấy trong lập trình OO, phải không?
rahulaga_dev

trong OO, bạn sẽ mong muốn object.Purchase () sửa đổi đối tượng. Bạn có thể làm cho nó bất biến, nhưng sau đó tại sao không chuyển sang mô hình Chức năng đầy đủ
Ewan

Tôi nghĩ vấn đề đang phải hình dung bởi vì nhà phát triển c # thuần túy là đối tượng được định hướng tự nhiên. Vì vậy, những gì bạn nói bằng ngôn ngữ bao gồm lập trình chức năng sẽ không yêu cầu hàm 'Purchasing () trả lại đơn đặt hàng đã mua để được đính kèm với bất kỳ lớp hoặc đối tượng nào, phải không?
rahulaga_dev

3
bạn có thể viết chức năng c # thay đổi đối tượng của bạn thành một cấu trúc, làm cho nó bất biến và viết một Func <Đặt hàng, Đặt hàng> Mua hàng
Ewan

12

Chìa khóa để hiểu tại sao các đối tượng bất biến có lợi không thực sự nằm trong việc cố gắng tìm các ví dụ cụ thể trong mã chức năng. Vì hầu hết các mã chức năng được viết bằng các ngôn ngữ chức năng và hầu hết các ngôn ngữ chức năng là bất biến theo mặc định, nên bản chất của mô hình được thiết kế để tránh những gì bạn đang tìm kiếm xảy ra.

Điều quan trọng để hỏi là, lợi ích của sự bất biến là gì? Câu trả lời là, nó tránh sự phức tạp. Nói rằng chúng ta có hai biến, xy. Cả hai đều bắt đầu với giá trị của 1. ymặc dù tăng gấp đôi cứ sau 13 giây. Giá trị của mỗi người trong số họ sẽ là bao nhiêu sau 20 ngày nữa? xsẽ 1. Điều đó thật dễ dàng. Nó sẽ mất nỗ lực mặc dù để giải quyết ynó phức tạp hơn. Thời gian nào trong ngày trong 20 ngày thời gian? Tôi có phải tiết kiệm ánh sáng ban ngày vào tài khoản không? Sự phức tạp của yso với xchỉ là nhiều hơn nữa.

Và điều này xảy ra trong mã thực sự quá. Mỗi khi bạn thêm một giá trị đột biến vào hỗn hợp, nó sẽ trở thành một giá trị phức tạp khác để bạn giữ và tính toán trong đầu, hoặc trên giấy, khi cố gắng viết, đọc hoặc gỡ lỗi mã. Càng phức tạp, cơ hội bạn mắc lỗi và giới thiệu lỗi càng lớn. Mã khó viết; khó để đọc; khó gỡ lỗi: mã khó lấy đúng.

Mutility không phải là xấu mặc dù. Một chương trình có khả năng biến đổi bằng không có thể không có kết quả, điều này khá vô dụng. Ngay cả khi khả năng biến đổi là ghi kết quả lên màn hình, đĩa hoặc bất cứ thứ gì, nó vẫn cần phải ở đó. Điều xấu là sự phức tạp không cần thiết. Một trong những cách đơn giản nhất để giảm độ phức tạp là làm cho mọi thứ trở nên bất biến theo mặc định và chỉ làm cho chúng có thể thay đổi khi cần, vì lý do hiệu suất hoặc chức năng.


4
"Một trong những cách đơn giản nhất để giảm độ phức tạp là làm cho mọi thứ trở nên bất biến theo mặc định và chỉ làm cho chúng có thể thay đổi khi cần thiết": Tóm tắt rất hay và ngắn gọn.
Giorgio

2
@DavidArno Sự phức tạp mà bạn mô tả làm cho mã khó lý do. Bạn cũng đã chạm vào điều này khi bạn nói "Mã này khó viết, khó đọc, khó gỡ lỗi; ...". Tôi thích các đối tượng bất biến bởi vì chúng tạo ra mã dễ dàng hơn nhiều để lý luận, không chỉ bởi bản thân tôi, mà cả các nhà quan sát nhìn vào mà không biết toàn bộ dự án.
tháo rời-số-5

1
@RahulAgarwal, " Nhưng tại sao vấn đề này trở nên nổi bật hơn trong bối cảnh lập trình chức năng ". Nó không. Tôi nghĩ có lẽ tôi đang bối rối bởi những gì bạn đang hỏi vì vấn đề ít nổi bật hơn ở FP vì FP khuyến khích sự bất biến do đó tránh được vấn đề.
David Arno

1
@djechlin, " Làm thế nào để ví dụ 13 giây của bạn trở nên dễ dàng hơn để phân tích với mã bất biến? " Không thể: yphải biến đổi; đó là một yêu cầu Đôi khi chúng ta phải có mã phức tạp để đáp ứng các yêu cầu phức tạp. Điểm tôi đang cố gắng thực hiện là sự phức tạp không cần thiết nên tránh. Các giá trị đột biến vốn đã phức tạp hơn các giá trị cố định, vì vậy - để tránh sự phức tạp không cần thiết - chỉ thay đổi các giá trị khi bạn phải thực hiện.
David Arno

3
Sự tương hỗ tạo ra một cuộc khủng hoảng danh tính. Biến của bạn không có một danh tính duy nhất nữa. Thay vào đó, danh tính của nó bây giờ phụ thuộc vào thời gian. Vì vậy, về mặt tượng trưng, ​​thay vì một x đơn, giờ chúng ta có một gia đình x_t. Bất kỳ mã nào sử dụng biến đó bây giờ cũng sẽ phải lo lắng về thời gian, gây ra sự phức tạp thêm được đề cập trong câu trả lời.
Alex Vong

8

Điều gì có thể đi sai trong bối cảnh lập trình chức năng

Những điều tương tự có thể sai trong lập trình phi chức năng: bạn có thể gặp các tác dụng phụ không mong muốn, không mong muốn, là nguyên nhân gây ra lỗi nổi tiếng kể từ khi phát minh ra các ngôn ngữ lập trình có phạm vi.

IMHO sự khác biệt thực sự duy nhất về điều này giữa lập trình chức năng và phi chức năng là, trong mã không chức năng, bạn thường mong đợi các tác dụng phụ, trong lập trình chức năng, bạn sẽ không.

Về cơ bản nếu nó là xấu, nó là xấu bất kể OO hoặc mô hình lập trình chức năng, phải không?

Chắc chắn - tác dụng phụ không mong muốn là một loại lỗi, bất kể mô hình. Điều ngược lại cũng đúng - các tác dụng phụ được sử dụng có chủ ý có thể giúp giải quyết các vấn đề về hiệu suất và thường cần thiết cho hầu hết các chương trình trong thế giới thực khi nói đến I / O và xử lý các hệ thống bên ngoài - bất kể mô hình.


4

Tôi vừa trả lời một câu hỏi StackOverflow minh họa câu hỏi của bạn khá tốt. Vấn đề chính với các cấu trúc dữ liệu có thể thay đổi là danh tính của chúng chỉ có giá trị tại một thời điểm chính xác, do đó mọi người có xu hướng nhồi nhét hết mức có thể vào điểm nhỏ trong mã nơi họ biết danh tính là hằng số. Trong ví dụ cụ thể này, nó đang thực hiện nhiều thao tác đăng nhập trong vòng lặp for:

for (elem <- rows map (row => s3 map row)) {
  val elem_str = elem.map(_.toString)

  logger.info("verifying the S3 bucket passed from the ctrl table for each App")
  logger.info(s"Checking on App Code: ${elem head}")

  listS3Buckets(elem_str(1), elem_str(2)) match {

    case Some(allBktsInfo) =>
      logger.info(s"App: ${elem_str head} provided the bucket name as: ${elem_str(3)}")
      if (allBktsInfo.exists(x => x.getName == elem_str(3))) {
        logger.info(s"Provided S3 bucket: ${elem_str(3)} exists")
        println(s"s3 ${elem_str(3)} bucket exists")
      } else {
        logger.info(s"WARNING: Provided S3 bucket ${elem_str(3)} doesn't exists")
        logger.info(s"WARNING: Dropping the App: ${elem_str.head} from backup schedule")
        excludeList += elem_str.head // If the bucket is invalid then we exclude from backup
        println(s"s3 bucket ${elem_str(3)} doesn't exists")
    }

    case None =>
      logger.info(s"WARNING: Provided S3 bucket ${elem_str(3)} doesn't exists")
      logger.info(s"WARNING: Dropping the App: ${elem_str.head} from backup schedule")
      excludeList += elem_str.head // If the bucket is invalid then we exclude from backup
}

Khi bạn đã quen với tính không thay đổi, sẽ không sợ cấu trúc dữ liệu thay đổi nếu bạn chờ đợi quá lâu, vì vậy bạn có thể thực hiện các nhiệm vụ tách biệt một cách hợp lý khi rảnh rỗi, theo cách tách rời hơn nhiều:

val (exists, missing) = rows partition bucketExists
missing foreach {row =>
  logger.info(s"WARNING: Provided S3 bucket ${row("s3_primary_bkt_name")} doesn't exist")
  logger.info(s"WARNING: Dropping the App: ${row("app")} from backup schedule")
}

3

Ưu điểm của việc sử dụng các đối tượng bất biến là nếu một người nhận được một tham chiếu đến một đối tượng có một thuộc tính nhất định khi người nhận kiểm tra nó và cần cung cấp một số mã khác tham chiếu đến một đối tượng có cùng thuộc tính đó, người ta có thể chỉ cần vượt qua dọc theo tham chiếu đến đối tượng mà không quan tâm đến ai khác có thể đã nhận được tham chiếu hoặc họ có thể làm gì với đối tượng [vì không ai khác có thể làm gì với đối tượng] hoặc khi người nhận có thể kiểm tra đối tượng [vì tất cả các thuộc tính sẽ giống nhau bất kể khi nào chúng được kiểm tra].

Ngược lại, mã cần cho ai đó tham chiếu đến một đối tượng có thể thay đổi sẽ có một thuộc tính nhất định khi người nhận kiểm tra nó (giả sử chính người nhận không thay đổi nó) hoặc cần phải biết rằng không có gì khác ngoài người nhận sẽ thay đổi tài sản đó, hoặc người khác biết khi nào người nhận sẽ truy cập vào tài sản đó và biết rằng sẽ không có gì thay đổi tài sản đó cho đến lần cuối cùng người nhận sẽ kiểm tra nó.

Tôi nghĩ rằng nó hữu ích nhất, để lập trình nói chung (không chỉ lập trình chức năng) nghĩ về các đối tượng bất biến như được chia thành ba loại:

  1. Các đối tượng không thể không cho phép bất cứ điều gì thay đổi chúng, ngay cả với một tham chiếu. Các đối tượng như vậy và các tham chiếu đến chúng, hoạt động như các giá trị và có thể được chia sẻ tự do.

  2. Các đối tượng sẽ cho phép bản thân được thay đổi bởi mã có tham chiếu đến chúng, nhưng các tham chiếu của chúng sẽ không bao giờ được tiếp xúc với bất kỳ mã nào thực sự thay đổi chúng. Các đối tượng này đóng gói các giá trị, nhưng chúng chỉ có thể được chia sẻ với mã có thể tin cậy để không thay đổi chúng hoặc đưa chúng ra mã có thể làm được.

  3. Các đối tượng sẽ được thay đổi. Các đối tượng này được xem tốt nhất dưới dạng các thùng chứa và tham chiếu đến chúng dưới dạng định danh .

Một mẫu hữu ích thường là để một đối tượng tạo ra một thùng chứa, điền vào nó bằng mã có thể tin cậy không giữ tham chiếu sau đó, và sau đó có các tham chiếu duy nhất tồn tại ở bất kỳ đâu trong vũ trụ sẽ không bao giờ sửa đổi đối tượng một khi nó được dân cư. Mặc dù container có thể là một loại có thể thay đổi, nhưng nó có thể được lý do về (*) như thể nó là bất biến, vì thực tế sẽ không có gì biến đổi nó. Nếu tất cả các tham chiếu đến vùng chứa được giữ trong các loại trình bao bọc không thay đổi sẽ không bao giờ thay đổi nội dung của nó, thì các trình bao bọc đó có thể được chuyển xung quanh một cách an toàn như thể dữ liệu trong chúng được giữ trong các đối tượng không thay đổi, vì các tham chiếu đến trình bao bọc có thể được chia sẻ và kiểm tra tự do tại Bất cứ lúc nào.

(*) Trong mã đa luồng, có thể cần phải sử dụng "hàng rào bộ nhớ" để đảm bảo rằng trước khi bất kỳ luồng nào có thể nhìn thấy bất kỳ tham chiếu nào đến trình bao bọc, tác động của tất cả các hành động trên vùng chứa sẽ hiển thị với luồng đó, nhưng đó là một trường hợp đặc biệt được đề cập ở đây chỉ cho sự hoàn chỉnh.


cảm ơn vì câu trả lời ấn tượng !! Tôi nghĩ có lẽ nguồn gốc của sự nhầm lẫn của tôi là do xuất phát từ nền tảng c # và đang học "viết mã kiểu chức năng trong c #", điều này khiến mọi người nói rằng tránh các đối tượng có thể thay đổi - nhưng tôi nghĩ các ngôn ngữ bao gồm mô hình lập trình chức năng thúc đẩy (hoặc thực thi - không chắc chắn nếu thực thi là chính xác để sử dụng) bất biến.
rahulaga_dev

@RahulAgarwal: Có thể có các tham chiếu đến một đối tượng gói gọn một giá trị mà ý nghĩa của nó không bị ảnh hưởng bởi sự tồn tại của các tham chiếu khác đến cùng một đối tượng, có một danh tính sẽ liên kết chúng với các tham chiếu khác cho cùng một đối tượng hoặc không. Nếu trạng thái từ thực thay đổi, thì giá trị hoặc danh tính của một đối tượng được liên kết với trạng thái đó có thể là hằng số, nhưng không phải cả hai - người ta sẽ phải thay đổi. 50.000 đô la là nên làm gì.
supercat

1

Như đã đề cập, vấn đề với trạng thái đột biến về cơ bản là một lớp con của vấn đề lớn hơn về tác dụng phụ , trong đó kiểu trả về của hàm không mô tả chính xác chức năng thực sự làm gì, vì trong trường hợp này, nó cũng gây đột biến trạng thái. Vấn đề này đã được giải quyết bởi một số ngôn ngữ nghiên cứu mới, chẳng hạn như F * ( http://www.fstar-lang.org/tutorial/ ). Ngôn ngữ này tạo ra một Hệ thống hiệu ứng tương tự như hệ thống loại, trong đó một hàm không chỉ khai báo tĩnh kiểu của nó, mà cả các hiệu ứng của nó. Bằng cách này, những người gọi của hàm nhận thức được rằng đột biến trạng thái có thể xảy ra khi gọi hàm và hiệu ứng đó được lan truyền đến những người gọi nó.

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.