Đơn nguyên
Một đơn nguyên bao gồm
Một endofunctor . Trong thế giới kỹ thuật phần mềm của chúng tôi, chúng tôi có thể nói điều này tương ứng với một kiểu dữ liệu với một tham số loại duy nhất, không bị hạn chế. Trong C #, đây sẽ là một cái gì đó có dạng:
class M<T> { ... }
Hai hoạt động được xác định trên kiểu dữ liệu đó:
return
/ pure
lấy một giá trị "thuần" (nghĩa là một T
giá trị) và "bọc" nó vào đơn nguyên (nghĩa là nó tạo ra một M<T>
giá trị). Vì return
là một từ khóa dành riêng trong C #, nên tôi sẽ sử dụng pure
để tham khảo thao tác này kể từ bây giờ. Trong C #, pure
sẽ là một phương thức có chữ ký như:
M<T> pure(T v);
bind
/ flatmap
lấy một giá trị đơn trị ( M<A>
) và một hàm f
. f
nhận một giá trị thuần túy và trả về một giá trị đơn trị ( M<B>
). Từ những điều này, bind
tạo ra một giá trị đơn âm mới ( M<B>
). bind
có chữ ký C # sau:
M<B> bind(M<A> mv, Func<A, M<B>> f);
Ngoài ra, để trở thành một đơn nguyên, pure
và bind
được yêu cầu phải tuân theo ba luật đơn nguyên.
Bây giờ, một cách để mô hình hóa các đơn nguyên trong C # sẽ là xây dựng giao diện:
interface Monad<M> {
M<T> pure(T v);
M<B> bind(M<A> mv, Func<A, M<B>> f);
}
(Lưu ý: Để giữ cho mọi thứ ngắn gọn và diễn cảm, tôi sẽ thực hiện một số quyền tự do với mã trong suốt câu trả lời này.)
Bây giờ chúng ta có thể triển khai các đơn nguyên cho các bảng dữ liệu cụ thể bằng cách triển khai các triển khai cụ thể của Monad<M>
. Chẳng hạn, chúng tôi có thể triển khai đơn nguyên sau cho IEnumerable
:
class IEnumerableM implements Monad<IEnumerable> {
IEnumerable<T> pure(T v) {
return (new List<T>(){v}).AsReadOnly();
}
IEnumerable<B> bind(IEnumerable<A> mv, Func<A, IEnumerable<B>> f) {
;; equivalent to mv.SelectMany(f)
return (from a in mv
from b in f(a)
select b);
}
}
(Tôi cố tình sử dụng cú pháp LINQ để gọi ra mối quan hệ giữa cú pháp LINQ và các đơn nguyên. Nhưng lưu ý rằng chúng ta có thể thay thế truy vấn LINQ bằng một cuộc gọi đến SelectMany
.)
Bây giờ, chúng ta có thể định nghĩa một đơn nguyên cho IObservable
? Có vẻ như vậy:
class IObservableM implements Monad<IObservable> {
IObservable<T> pure(T v){
Observable.Return(v);
}
IObservable<B> bind(IObservable<A> mv, Func<A, IObservable<B>> f){
mv.SelectMany(f);
}
}
Để chắc chắn rằng chúng ta có một đơn nguyên, chúng ta cần chứng minh các luật đơn nguyên. Điều này có thể không tầm thường (và tôi không đủ quen thuộc với Rx.NET để biết liệu chúng có thể được chứng minh chỉ từ thông số kỹ thuật không), nhưng đó là một khởi đầu đầy hứa hẹn. Để tạo điều kiện cho phần còn lại của cuộc thảo luận này, chúng ta hãy giả sử các luật đơn nguyên giữ trong trường hợp này.
Đơn nguyên miễn phí
Không có "đơn nguyên" duy nhất. Thay vào đó, các đơn vị tự do là một lớp các đơn vị được xây dựng từ functor. Đó là, được đưa ra một functor F
, chúng ta có thể tự động lấy ra một đơn nguyên cho F
(nghĩa là đơn vị tự do F
).
Chức năng
Giống như các đơn nguyên, functor có thể được xác định bởi ba mục sau:
- Một kiểu dữ liệu, được tham số hóa qua một biến loại đơn, không giới hạn.
Hai hoạt động:
pure
kết thúc một giá trị thuần túy vào functor. Điều này là tương tự pure
cho một đơn nguyên. Trong thực tế, đối với các functor cũng là một đơn nguyên, cả hai nên giống hệt nhau.
fmap
ánh xạ các giá trị trong đầu vào thành các giá trị mới trong đầu ra thông qua một chức năng nhất định. Chữ ký của nó là:
F<B> fmap(Func<A, B> f, F<A> fv)
Giống như các đơn nguyên, functor được yêu cầu phải tuân theo luật functor.
Tương tự như monads, chúng ta có thể mô hình functor thông qua giao diện sau:
interface Functor<F> {
F<T> pure(T v);
F<B> fmap(Func<A, B> f, F<A> fv);
}
Bây giờ, vì các đơn nguyên là một lớp con của functor, chúng ta cũng có thể cấu trúc lại Monad
một chút:
interface Monad<M> extends Functor<M> {
M<T> join(M<M<T>> mmv) {
Func<T, T> identity = (x => x);
return mmv.bind(x => x); // identity function
}
M<B> bind(M<A> mv, Func<A, M<B>> f) {
join(fmap(f, mv));
}
}
Ở đây tôi đã thêm một phương thức bổ sung join
và cung cấp các cài đặt mặc định của cả hai join
và bind
. Lưu ý, tuy nhiên, đây là những định nghĩa tròn. Vì vậy, bạn phải ghi đè ít nhất một hoặc khác. Ngoài ra, lưu ý rằng pure
bây giờ được kế thừa từ Functor
.
IObservable
và đơn nguyên miễn phí
Bây giờ, vì chúng ta đã định nghĩa một đơn nguyên cho IObservable
và vì các đơn vị là một lớp con của hàm functor, do đó chúng ta phải có thể định nghĩa một thể hiện functor cho IObservable
. Đây là một định nghĩa:
class IObservableF implements Functor<IObservable> {
IObservable<T> pure(T v) {
return Observable.Return(v);
}
IObservable<B> fmap(Func<A, B> f, IObservable<A> fv){
return fv.Select(f);
}
}
Bây giờ chúng ta có một functor được xác định cho IObservable
, chúng ta có thể xây dựng một đơn nguyên miễn phí từ functor đó. Và đó chính xác là cách IObservable
liên quan đến các đơn vị tự do - cụ thể là, chúng ta có thể xây dựng một đơn vị tự do từ đó IObservable
.
Cont
là đơn nguyên duy nhất tôi đã thấy đề xuất rằng không thể được thể hiện thông qua đơn nguyên miễn phí, có lẽ người ta có thể cho rằng FRP có thể. Như có thể hầu hết mọi thứ khác .