Có tồn tại một ngôn ngữ lập trình được thiết kế đặc biệt cho tiêm phụ thuộc?


21

Nhiều ngôn ngữ lập trình chung đủ linh hoạt để cho phép bạn hỗ trợ tiêm phụ thuộc. Ngay cả khi không có thư viện hoặc khung hỗ trợ. Nhưng ngay cả khi một ngôn ngữ là Turing hoàn chỉnh đủ để giải quyết bất kỳ vấn đề lập trình nào, một ngôn ngữ sẽ đưa ra các lựa chọn tác động đến những gì dễ dàng và những gì khó thực hiện trong chúng.

Có ngôn ngữ nào được thiết kế đặc biệt để làm cho việc tiêm phụ thuộc trở nên dễ dàng, và ngược lại, làm cho việc tạo các phụ thuộc ẩn trở nên khó khăn không?

Làm rõ:

Do những hạn chế của một số ngôn ngữ (nhìn vào Java của bạn), nhiều người coi hỗ trợ về hệ thống dây điện và xây dựng là một phần của việc tiêm phụ thuộc. Ở đây tôi chỉ có ý định một ngôn ngữ được thiết kế cho DI có nghĩa là sự phụ thuộc không dễ bị ẩn trong các tác dụng phụ. Có một quy ước về hệ thống cấu hình là tốt sẽ chỉ được thêm nước thịt.

Tôi không tìm kiếm một đề nghị ngôn ngữ. Đó là một câu hỏi lịch sử. Có tác giả ngôn ngữ nào đã từng đặt ra một cách rõ ràng để làm điều này?


1

Whee! 3K! Bây giờ tôi có thể xem họ bỏ phiếu để đóng câu hỏi này. :) Cảm ơn đã bình chọn.
candied_orange

1
Dù sao bạn cũng nên đọc bài viết đó. "Liệu nó có tồn tại" là một câu hỏi ít thú vị hơn, trừ khi bạn mở rộng nó thành một cái gì đó như "họ đã làm như thế nào?"
Robert Harvey

1
Haskell sẽ tính? Với các hàm currying và bậc cao hơn, bạn có thể giải quyết hầu hết các vấn đề mà DI thường giải quyết bằng các ngôn ngữ OOP và với sự nghiêm ngặt của nó về độ tinh khiết, bạn buộc phải tách các tác dụng phụ như IO, v.v. Bạn không nhận được bất kỳ quy ước nào về cấu hình, nhưng mặt khác, tôi đang dần rời xa điều đó ngay cả trong mã OOP của tôi ngày nay vì tôi nhận thấy rằng hầu hết các đội không thể tin tưởng vào điều đó đối với các dự án cỡ trung bình và lớn hơn.
wasatz

1
@wasatz: Vâng, Haskell tính. Hầu hết các "mẫu phần mềm" thực sự chỉ là cách giải quyết cho những thiếu sót trong ngôn ngữ lập trình.
Robert Harvey

Câu trả lời:


21

Vâng, thực sự có. Sắp xếp

Drameak không có trạng thái tĩnh và không có trạng thái toàn cầu. Điều này có nghĩa là cách khả thi duy nhất để có quyền truy cập vào một phụ thuộc là tiêm nó một cách rõ ràng. Rõ ràng, điều này có nghĩa là ngôn ngữ, hoặc trong trường hợp của Fileak chính xác hơn, IDE cần làm cho việc tiêm phụ thuộc trở nên dễ dàng, nếu không ngôn ngữ sẽ không thể sử dụng được.

Vì vậy, ngôn ngữ không được thiết kế cho DI, thay vào đó, sự cần thiết cho DI là hệ quả của thiết kế ngôn ngữ.

Nếu không có trạng thái tĩnh và không có trạng thái toàn cầu, thì bạn không thể "tiếp cận" với ether và rút thứ gì đó ra. Ví dụ, trong Java, cấu trúc gói là trạng thái tĩnh. Tôi chỉ có thể nói java.lang.Stringvà tôi có Stringlớp học. Điều đó là không thể trong Drameak. Mọi thứ bạn làm việc cùng, phải được cung cấp rõ ràng cho bạn, nếu không bạn không thể có được nó. Vì vậy, tất cả mọi thứ là một phụ thuộc, và mọi phụ thuộc là rõ ràng.

Bạn muốn một chuỗi? Chà, trước tiên bạn phải yêu cầu stdlibđối tượng trao cho bạn Stringlớp. Oh, nhưng làm thế nào để bạn có quyền truy cập vào stdlib? Vâng, trước tiên bạn phải yêu cầu đưa platformcho bạn stdlibđối tượng. Oh, nhưng làm thế nào để bạn có quyền truy cập vào platform? Vâng, trước tiên bạn phải yêu cầu người khác đưa cho bạn platformđối tượng. Ồ, nhưng làm thế nào để bạn có quyền truy cập vào một người nào đó? Vâng, trước tiên bạn phải yêu cầu một người khác đưa cho bạn đối tượng.

Cái hố thỏ này đi được bao xa? Đâu là đệ quy dừng lại? Tất cả các cách, thực sự. Nó không dừng lại. Sau đó, làm thế nào bạn có thể viết một chương trình trong Drameak? Vâng, nói đúng ra, bạn không thể!

Bạn cần một số thực thể bên ngoài gắn kết tất cả lại với nhau. Trong Drameak, thực thể đó là IDE. IDE nhìn thấy toàn bộ chương trình. Nó có thể nối các mảnh khác nhau lại với nhau. Mẫu tiêu chuẩn trong Drameak là lớp trung tâm của ứng dụng của bạn có một trình truy cập được gọi platformvà IDEeakeak đưa một đối tượng vào trình truy cập đó có các phương thức trả về một số nhu cầu cơ bản của lập trình: một Stringlớp, một Numberlớp, một Arraylớp, vân vân

Nếu bạn muốn kiểm tra ứng dụng của mình, bạn có thể tiêm một platformđối tượng có Filephương thức trả về một lớp bằng các phương thức giả. Nếu bạn muốn triển khai ứng dụng của mình lên đám mây, bạn tiêm một nền tảng có Filelớp thực sự được hỗ trợ bởi Amazon S3. GUI đa nền tảng hoạt động bằng cách tiêm các khung GUI khác nhau cho các HĐH khác nhau. Drameak thậm chí còn có trình biên dịch thử nghiệm từ Fileak-to-ECMAScript và khung GUI được hỗ trợ HTML cho phép bạn chuyển một ứng dụng GUI có đầy đủ tính năng từ máy tính để bàn vào trình duyệt mà không thay đổi, chỉ bằng cách tiêm các yếu tố GUI khác nhau.

Nếu bạn muốn triển khai ứng dụng của mình, IDE có thể tuần tự hóa ứng dụng thành một đối tượng trên đĩa. (Không giống như tổ tiên của nó, Smalltalk, Drameak có định dạng tuần tự hóa đối tượng ngoài hình ảnh. Bạn không cần phải mang theo toàn bộ hình ảnh, vì tất cả các phụ thuộc đều được đưa vào: IDE biết chính xác phần nào trong ứng dụng của bạn sử dụng và không sử dụng. Vì vậy, nó tuần tự hóa chính xác sơ đồ con được kết nối của không gian đối tượng bao gồm ứng dụng của bạn, không có gì nữa.)

Tất cả những điều này hoạt động đơn giản bằng cách đưa hướng đối tượng đến mức cực đoan: mọi thứ đều là một cuộc gọi phương thức ảo ("gửi tin nhắn" theo thuật ngữ của Smalltalk, trong đó Drameak là hậu duệ). Ngay cả việc tra cứu siêu lớp cũng là một cuộc gọi phương thức ảo! Lấy một cái gì đó như

class Foo extends Bar // using Java syntax for familiarity

hoặc, trong tờ báo:

class Foo = Bar () () : ()

Trong Java, điều này sẽ tạo một tên Footrong không gian tên toàn cục tĩnh và tìm kiếm Bartrong không gian tên toàn cầu tĩnh và tạo ra Bar Foosiêu lớp. Ngay cả trong Ruby, năng động hơn nhiều, điều này vẫn sẽ tạo ra một hằng số tĩnh trong không gian tên toàn cầu.

Trong Drameak, khai báo tương đương có nghĩa là: tạo một phương thức getter có tên Foovà làm cho nó trả về một lớp tìm kiếm siêu lớp của nó bằng cách gọi phương thức có tên Bar. Lưu ý: điều này không giống với Ruby, nơi bạn có thể đặt bất kỳ mã Ruby thực thi nào làm khai báo siêu lớp, nhưng mã sẽ chỉ được thực thi một lần khi lớp được tạo và giá trị trả về của mã đó trở thành siêu lớp cố định. Không. Phương thức Barnày được gọi cho mỗi lần tra cứu phương thức!

Điều này có một số ý nghĩa sâu sắc:

  • vì mixin về cơ bản là một lớp chưa biết đến siêu lớp của nó, và trong Drameak, siêu lớp là một cuộc gọi phương thức ảo động, và do đó không biết, mỗi lớp cũng tự động là một mixin. Bạn nhận được mixins miễn phí.
  • vì một lớp bên trong chỉ là một lệnh gọi phương thức trả về một lớp, bạn có thể ghi đè phương thức đó trong một lớp con của lớp bên ngoài, vì vậy mọi lớp đều là ảo. Bạn nhận được các lớp ảo miễn phí:

    class Outer {
      class Inner { /* … */ }
    }
    
    class Sub extends Outer {
      override class Inner { /* … */ }
    }
    

    Báo chí:

    class Outer = () (
      class Inner = () () : ()
    ) : ()
    
    class Sub = Outer () (
      class Inner = () () : ()
    ) : ()
    
  • vì siêu lớp chỉ là một lệnh gọi phương thức trả về một lớp, nên bạn có thể ghi đè phương thức đó trong một lớp con của lớp bên ngoài, các lớp bên trong được định nghĩa trong lớp cha có thể có một siêu lớp khác trong lớp con. Bạn nhận được sự kế thừa phân cấp lớp miễn phí:

    class Outer {
      class MyCoolArray extends Array { /* … */ }
    }
    
    class Sub extends Outer {
      override class Array { /* … */ }
      // Now, for instances of `Sub`, `MyCoolArray` has a different superclass 
      // than for instances of `Outer`!!!
    }
    

    Báo chí:

    class Outer = () (
      class MyCoolArray = Array () () : ()
    ) : ()
    
    class Sub = Outer () (
      class Array = () () : ()
    ) : ()
    
  • và cuối cùng, điều quan trọng nhất cho cuộc thảo luận này: vì (ngoài những thứ bạn đã xác định trong lớp, rõ ràng), bạn chỉ có thể gọi các phương thức trong (các) lớp bao quanh từ vựng và siêu lớp của bạn, một lớp ngoài cùng cấp cao nhất không thể gọi bất kỳ phương pháp nào cả ngoại trừ những cái được tiêm một cách rõ ràng: một lớp học cấp cao nhất không có một lớp kèm theo mà phương pháp này có thể gọi điện thoại, và nó không thể có một lớp cha khác so với cái mặc định, vì việc khai báo lớp cha là một gọi phương thức, và rõ ràng là nó không thể đi đến siêu lớp (nó siêu lớp) và nó cũng không thể đi đến lớp bao quanh từ vựng, bởi vì không có cái nào cả. Điều này có nghĩa là các lớp cấp cao nhất được đóng gói hoàn toàn, họ chỉ có thể truy cập vào những gì họ được tiêm một cách rõ ràng và họ chỉ được tiêm những gì họ yêu cầu rõ ràng. Nói cách khác: các lớp cấp cao nhất là các mô-đun. Bạn nhận được toàn bộ hệ thống mô-đun miễn phí. Trong thực tế, để chính xác hơn: các lớp cấp cao nhất là các khai báo mô-đun, các thể hiện của nó là các mô-đun. Vì vậy, bạn có được một hệ thống mô-đun với các khai báo mô-đun tham số và các mô-đun hạng nhất miễn phí, điều mà nhiều hệ thống mô-đun thậm chí rất tinh vi không thể làm được.

Để làm cho tất cả các mũi tiêm này không đau, các khai báo lớp có cấu trúc bất thường: chúng bao gồm hai khai báo. Một là hàm tạo của lớp, không phải là hàm tạo xây dựng các thể hiện của lớp, mà là hàm tạo xây dựng môi trường trong đó phần thân lớp chạy. Trong một cú pháp giống như Java, nó sẽ trông giống như thế này:

class Foo(platform) extends Bar {
  Array  = platform.collections.Array
  String = platform.lang.String
  File   = platform.io.File
| // separator between class constructor and class body
  class MyArray extends Array { /* … */ }
  // Array refers to the method defined above which in turn gets it from the 
  // platform object that was passed into the class "somehow"
}

Báo chí:

class Foo using: platform = Bar (
  Array = platform collections Array
  String = platform streams String 
  File = platform files ExternalReadWriteStream
) (
  class MyArray = Array () () : ()
) : ()

Lưu ý rằng cách một lập trình viên của tạp chí thực sự sẽ thấy (các) lớp là như thế này:IDEeakeak hiển thị nhiều lớp lồng nhau

Tôi thậm chí không thể bắt đầu làm điều đó công lý, mặc dù. Bạn sẽ phải tự chơi xung quanh nó. Gilad Bracha đã đưa ra một vài cuộc nói chuyện về các khía cạnh khác nhau của hệ thống, bao gồm cả mô-đun. Ông đã có một cuộc nói chuyện thực sự dài (2 giờ) , giờ đầu tiên là phần giới thiệu kỹ lưỡng về ngôn ngữ, bao gồm cả câu chuyện mô đun. Chương 2 của Nền tảng lập trình báo cáo bao gồm mô-đun. Nếu bạn đọc lướt trên Squeak - Hướng dẫn cho sự bối rối (hay còn gọi là Drameak-101) , bạn sẽ có cảm giác về hệ thống. Báo cáo bằng ví dụ là một tài liệu trực tiếp (tức là nó đang chạy bên trong cổng Drameak-on-ECMASCript, mọi dòng mã đều có thể chỉnh sửa, mọi kết quả đều có thể kiểm tra được) thể hiện cú pháp cơ bản.

Nhưng thực sự, bạn phải chơi xung quanh nó. Nó chỉ khác với tất cả các ngôn ngữ chính thống và thậm chí hầu hết các ngôn ngữ không chính thống đến mức khó giải thích, nó phải được trải nghiệm.


3
Meh. Cấm sử dụng trạng thái tĩnh và trạng thái toàn cầu, và bạn có thể nói điều này về hầu hết mọi ngôn ngữ lập trình hiện đại.
Robert Harvey

Tò mò, nhiều tay tiêm làm bằng tay của tôi là các nhà máy tĩnh. Trước đây không nghĩ đó là một điều xấu.
candied_orange

@ Jörg Có cách nào bạn có thể sao lưu câu trả lời này thêm một chút không? Tôi đã googled "tiêm phụ thuộc báo chí" và đã bỏ trống. Điều gần nhất tôi có thể tìm thấy là đây: news.ycombinator.com/item?id=9620561
candied_orange

@CandiedOrange: Tôi đang trong quá trình mở rộng câu trả lời nhưng sau đó "thế giới thực" đã can thiệp. Điều đó có tốt hơn không?
Jörg W Mittag

3
@ JörgWMittag Holy crap! Chà, nó chắc chắn là "nhiều hơn". Đợi trong khi tôi đánh giá "tốt hơn". Có thể cần sức mạnh của một chuyến thăm phòng tắm để vượt qua điều này.
candied_orange

7

Ngôn ngữ lập trình Wake được thiết kế để sử dụng phép nội xạ phụ thuộc. Về cơ bản, nó có tương đương với một khung tiêm phụ thuộc được đưa vào ngôn ngữ. Các lớp định nghĩa các tham số họ needprovidetrình biên dịch nối mọi thứ lên.


6

Đây không phải là một ngôn ngữ thực tế hữu ích, nhưng hệ thống được mô tả trong bài viết này. Có một hiệu ứng thú vị: nó cho phép bạn viết một lớp trừu tượng bằng cách sử dụng các lớp / giao diện trừu tượng (bao gồm cả khởi tạo chúng) Lớp của bạn sau đó có thể được tạo ra cụ thể bằng cách thay thế một lớp con của mỗi lớp trừu tượng bạn đã sử dụng tại điểm khởi tạo. Làm như vậy sẽ loại bỏ nhu cầu tiêm phụ thuộc trong ít nhất là các trường hợp đơn giản (sử dụng phiên bản giả định của Java được mở rộng với tính năng này), chúng tôi có thể lấy mã này:

public interface I {
    void something ();
)

public class Concrete implements I {
    public void something () { ... }
}

public class Client {
    I myI;
    public Client (I injected) { myI = injected; }
    ...
}

...

    Client c = new Client (new Concrete());
    ...

và thay thế Client và cách sử dụng của nó bằng:

public class Client {
   I myI = new I();
   ...
}

   Client c = new Client { I -> Concrete } ();

Lưu ý rằng điều này đơn giản hóa điểm sử dụng của một phụ thuộc, thay vì tạo. Nó cũng cho phép chúng tôi tránh mô hình Factory (vì tôi có thể được tạo theo yêu cầu bất cứ khi nào chúng tôi muốn).


Hấp dẫn. Điều này làm tôi nhớ đến các Thành viên Loại của Scala, cũng có thể bị ghi đè trong các lớp con và để lại trừu tượng trong một siêu lớp. Các thành viên loại trừu tượng kết hợp với chú thích tự loại tạo thành cơ sở cho phương pháp tiếp cận mô đun hóa và phụ thuộc của Scala. Tôi hoàn toàn không ngạc nhiên khi bài báo này đã được trích dẫn bởi cả Martin Oderky , nhà thiết kế của Scala và Gilad Bracha , nhà thiết kế của Drameak.
Jörg W Mittag

0

Tôi chưa sử dụng nó, nhưng khẩu hiệu chính thức của ngôn ngữ lập trình Nhựa là " Điều gì xảy ra nếu bạn tiêm thuốc phụ thuộc và nướng nó thành ngôn ngữ lập trình? ". Có vẻ khá thú vị

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.