Giống như người bắt trẻ trong Chitty-Chitty-Bang-Bang dụ trẻ em bị giam cầm bằng đồ ngọt và đồ chơi, những người tuyển dụng vào ngành Vật lý bậc đại học thích đánh lừa bằng bong bóng xà phòng và boomerang, nhưng khi cánh cửa đóng lại, đó là "Đúng rồi, các em, đã đến lúc học về phân hóa từng phần! ”. Tôi cũng vậy. Đừng nói rằng tôi đã không cảnh báo bạn.
Đây là một cảnh báo khác: mã sau đây cần {-# LANGUAGE KitchenSink #-}
, hay đúng hơn là
{-# LANGUAGE TypeFamilies, FlexibleContexts, TupleSections, GADTs, DataKinds,
TypeOperators, FlexibleInstances, RankNTypes, ScopedTypeVariables,
StandaloneDeriving, UndecidableInstances #-}
không theo thứ tự đặc biệt.
Các bộ chức năng khác biệt có khóa kéo hài hòa
Dù sao thì một functor có thể phân biệt được là gì?
class (Functor f, Functor (DF f)) => Diff1 f where
type DF f :: * -> *
upF :: ZF f x -> f x
downF :: f x -> f (ZF f x)
aroundF :: ZF f x -> ZF f (ZF f x)
data ZF f x = (:<-:) {cxF :: DF f x, elF :: x}
Đó là một functor có dẫn xuất, cũng là một functor. Đạo hàm đại diện cho ngữ cảnh một lỗ cho một phần tử . Loại dây kéo ZF f x
đại diện cho cặp bối cảnh một lỗ và phần tử trong lỗ.
Các thao tác để Diff1
mô tả các loại điều hướng mà chúng ta có thể thực hiện trên dây kéo (không có bất kỳ khái niệm nào về "sang trái" và "sang phải", hãy xem bài báo Clown and Jokers của tôi ). Chúng ta có thể đi lên "phía trên", lắp ráp lại cấu trúc bằng cách cắm phần tử vào lỗ của nó. Chúng ta có thể "đi xuống", tìm mọi cách để truy cập một phần tử trong cấu trúc cho trước: chúng tôi trang trí mọi phần tử với ngữ cảnh của nó. Chúng tôi có thể đi "xung quanh", lấy một dây kéo hiện có và trang trí từng yếu tố với bối cảnh của nó, vì vậy chúng tôi tìm mọi cách để lấy lại sự tập trung (và cách giữ trọng tâm hiện tại của chúng tôi).
Bây giờ, loại aroundF
có thể nhắc nhở một số bạn về
class Functor c => Comonad c where
extract :: c x -> x
duplicate :: c x -> c (c x)
và bạn đúng khi được nhắc nhở! Chúng tôi có, với một bước nhảy và một lần bỏ qua,
instance Diff1 f => Functor (ZF f) where
fmap f (df :<-: x) = fmap f df :<-: f x
instance Diff1 f => Comonad (ZF f) where
extract = elF
duplicate = aroundF
và chúng tôi nhấn mạnh rằng
extract . duplicate == id
fmap extract . duplicate == id
duplicate . duplicate == fmap duplicate . duplicate
Chúng tôi cũng cần điều đó
fmap extract (downF xs) == xs
fmap upF (downF xs) = fmap (const xs) xs
Hàm đa thức có thể phân biệt được
Các chức năng liên tục có thể phân biệt được.
data KF a x = KF a
instance Functor (KF a) where
fmap f (KF a) = KF a
instance Diff1 (KF a) where
type DF (KF a) = KF Void
upF (KF w :<-: _) = absurd w
downF (KF a) = KF a
aroundF (KF w :<-: _) = absurd w
Không có nơi nào để đặt một phần tử, vì vậy không thể tạo bối cảnh. Không có nơi nào để đi upF
hoặc về downF
, và chúng tôi dễ dàng tìm thấy tất cả các con đường để đi downF
.
Chức năng nhận dạng có thể phân biệt được.
data IF x = IF x
instance Functor IF where
fmap f (IF x) = IF (f x)
instance Diff1 IF where
type DF IF = KF ()
upF (KF () :<-: x) = IF x
downF (IF x) = IF (KF () :<-: x)
aroundF z@(KF () :<-: x) = KF () :<-: z
Có một yếu tố trong một bối cảnh tầm thường, downF
tìm nó, đóng upF
gói lại và aroundF
chỉ có thể giữ nguyên.
Tính tổng bảo toàn tính khác biệt.
data (f :+: g) x = LF (f x) | RF (g x)
instance (Functor f, Functor g) => Functor (f :+: g) where
fmap h (LF f) = LF (fmap h f)
fmap h (RF g) = RF (fmap h g)
instance (Diff1 f, Diff1 g) => Diff1 (f :+: g) where
type DF (f :+: g) = DF f :+: DF g
upF (LF f' :<-: x) = LF (upF (f' :<-: x))
upF (RF g' :<-: x) = RF (upF (g' :<-: x))
Các bit và mảnh khác là một số ít. Để bắt đầu downF
, chúng ta phải vào downF
bên trong thành phần được gắn thẻ, sau đó sửa các khóa kéo kết quả để hiển thị thẻ trong ngữ cảnh.
downF (LF f) = LF (fmap (\ (f' :<-: x) -> LF f' :<-: x) (downF f))
downF (RF g) = RF (fmap (\ (g' :<-: x) -> RF g' :<-: x) (downF g))
Để bắt đầu aroundF
, chúng tôi tách thẻ, tìm ra cách đi vòng quanh thứ không được gắn thẻ, sau đó khôi phục thẻ trong tất cả các khóa kéo kết quả. Yếu tố được chú trọng, x
được thay thế bằng toàn bộ dây kéo của nó , z
.
aroundF z@(LF f' :<-: (x :: x)) =
LF (fmap (\ (f' :<-: x) -> LF f' :<-: x) . cxF $ aroundF (f' :<-: x :: ZF f x))
:<-: z
aroundF z@(RF g' :<-: (x :: x)) =
RF (fmap (\ (g' :<-: x) -> RF g' :<-: x) . cxF $ aroundF (g' :<-: x :: ZF g x))
:<-: z
Lưu ý rằng tôi đã phải sử dụng ScopedTypeVariables
để phân biệt các cuộc gọi đệ quy tới aroundF
. Là một loại chức năng, DF
không bị thương, vì vậy thực tế f' :: D f x
là không đủ để buộc f' :<-: x :: Z f x
.
Sản phẩm bảo toàn tính khác biệt.
data (f :*: g) x = f x :*: g x
instance (Functor f, Functor g) => Functor (f :*: g) where
fmap h (f :*: g) = fmap h f :*: fmap h g
Để tập trung vào một phần tử trong một cặp, bạn lấy nét ở bên trái và để nguyên bên phải hoặc ngược lại. Quy tắc sản phẩm nổi tiếng của Leibniz tương ứng với một trực giác không gian đơn giản!
instance (Diff1 f, Diff1 g) => Diff1 (f :*: g) where
type DF (f :*: g) = (DF f :*: g) :+: (f :*: DF g)
upF (LF (f' :*: g) :<-: x) = upF (f' :<-: x) :*: g
upF (RF (f :*: g') :<-: x) = f :*: upF (g' :<-: x)
Bây giờ, downF
hoạt động tương tự như cách nó đã làm đối với tổng, ngoại trừ việc chúng ta phải sửa ngữ cảnh khóa kéo không chỉ với một thẻ (để hiển thị cách chúng ta đã đi) mà còn với thành phần khác chưa được chạm.
downF (f :*: g)
= fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x) (downF f)
:*: fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x) (downF g)
Nhưng aroundF
là một túi cười lớn. Cho dù chúng tôi đang truy cập bên nào, chúng tôi có hai lựa chọn:
- Di chuyển
aroundF
về phía đó.
- Di chuyển
upF
ra khỏi bên đó và downF
sang bên kia.
Mỗi trường hợp yêu cầu chúng tôi sử dụng các hoạt động cho cấu trúc con, sau đó sửa các ngữ cảnh.
aroundF z@(LF (f' :*: g) :<-: (x :: x)) =
LF (fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x)
(cxF $ aroundF (f' :<-: x :: ZF f x))
:*: fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x) (downF g))
:<-: z
where f = upF (f' :<-: x)
aroundF z@(RF (f :*: g') :<-: (x :: x)) =
RF (fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x) (downF f) :*:
fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x)
(cxF $ aroundF (g' :<-: x :: ZF g x)))
:<-: z
where g = upF (g' :<-: x)
Phù! Tất cả các đa thức đều có thể phân biệt được và do đó cung cấp cho chúng ta các số viết tắt.
Hừ! Tất cả đều hơi trừu tượng. Vì vậy, tôi đã thêm deriving Show
mọi nơi tôi có thể và đưa vào
deriving instance (Show (DF f x), Show x) => Show (ZF f x)
cho phép tương tác sau (thu dọn bằng tay)
> downF (IF 1 :*: IF 2)
IF (LF (KF () :*: IF 2) :<-: 1) :*: IF (RF (IF 1 :*: KF ()) :<-: 2)
> fmap aroundF it
IF (LF (KF () :*: IF (RF (IF 1 :*: KF ()) :<-: 2)) :<-: (LF (KF () :*: IF 2) :<-: 1))
:*:
IF (RF (IF (LF (KF () :*: IF 2) :<-: 1) :*: KF ()) :<-: (RF (IF 1 :*: KF ()) :<-: 2))
Bài tập Hãy chứng minh rằng thành phần của các chức năng phân biệt có thể phân biệt được, sử dụng quy tắc dây chuyền .
Ngọt! Chúng ta có thể về nhà bây giờ không? Dĩ nhiên là không. Chúng tôi chưa phân biệt bất kỳ cấu trúc đệ quy nào.
Tạo bộ giải mã đệ quy từ bifunctors
A Bifunctor
, như tài liệu hiện có về lập trình chung kiểu dữ liệu (xem công trình của Patrik Jansson và Johan Jeuring, hoặc các ghi chú bài giảng xuất sắc của Jeremy Gibbons) giải thích về độ dài là một phương thức khởi tạo kiểu có hai tham số, tương ứng với hai loại cấu trúc con. Chúng ta sẽ có thể "lập bản đồ" cả hai.
class Bifunctor b where
bimap :: (x -> x') -> (y -> y') -> b x y -> b x' y'
Chúng ta có thể sử dụng Bifunctor
s để đưa ra cấu trúc nút của vùng chứa đệ quy. Mỗi nút có subnodes và yếu tố . Đây chỉ có thể là hai loại cấu trúc con.
data Mu b y = In (b (Mu b y) y)
Xem? Chúng ta "thắt nút đệ quy" trong b
đối số đầu tiên của nó và giữ tham số y
ở thứ hai. Theo đó, chúng tôi có được một lần cho tất cả
instance Bifunctor b => Functor (Mu b) where
fmap f (In b) = In (bimap (fmap f) f b)
Để sử dụng điều này, chúng tôi sẽ cần một bộ các Bifunctor
phiên bản.
Bộ Bifunctor
Hằng số là hai mặt.
newtype K a x y = K a
instance Bifunctor (K a) where
bimap f g (K a) = K a
Bạn có thể nói rằng tôi đã viết đoạn này đầu tiên, bởi vì các mã nhận dạng ngắn hơn, nhưng điều đó tốt vì mã dài hơn.
Các biến là hai mặt.
Chúng tôi cần các bifunctors tương ứng với một tham số này hoặc tham số khác, vì vậy tôi đã tạo một kiểu dữ liệu để phân biệt chúng, sau đó xác định một GADT phù hợp.
data Var = X | Y
data V :: Var -> * -> * -> * where
XX :: x -> V X x y
YY :: y -> V Y x y
Điều đó tạo ra V X x y
một bản sao của x
và V Y x y
một bản sao của y
. Theo đó
instance Bifunctor (V v) where
bimap f g (XX x) = XX (f x)
bimap f g (YY y) = YY (g y)
Sums và Sản phẩm của bifunctors là bifunctors
data (:++:) f g x y = L (f x y) | R (g x y) deriving Show
instance (Bifunctor b, Bifunctor c) => Bifunctor (b :++: c) where
bimap f g (L b) = L (bimap f g b)
bimap f g (R b) = R (bimap f g b)
data (:**:) f g x y = f x y :**: g x y deriving Show
instance (Bifunctor b, Bifunctor c) => Bifunctor (b :**: c) where
bimap f g (b :**: c) = bimap f g b :**: bimap f g c
Cho đến nay, bảng soạn sẵn, nhưng bây giờ chúng ta có thể xác định những thứ như
List = Mu (K () :++: (V Y :**: V X))
Bin = Mu (V Y :**: (K () :++: (V X :**: V X)))
Nếu bạn muốn sử dụng các kiểu này cho dữ liệu thực tế và không đi theo truyền thống pointilliste của Georges Seurat, hãy sử dụng các từ đồng nghĩa mẫu .
Nhưng khóa kéo là gì? Làm thế nào chúng ta sẽ cho thấy rằng Mu b
có thể phân biệt được? Chúng ta cần chỉ ra rằng b
có thể phân biệt được ở cả hai biến. Kêu vang! Đã đến lúc học về sự khác biệt từng phần.
Các dẫn xuất một phần của phân tử
Bởi vì chúng ta có hai biến số, chúng ta sẽ cần có thể nói về chúng một cách tập thể đôi khi và riêng lẻ vào những thời điểm khác. Chúng ta sẽ cần họ singleton:
data Vary :: Var -> * where
VX :: Vary X
VY :: Vary Y
Bây giờ chúng ta có thể nói ý nghĩa của việc Bifunctor có các đạo hàm riêng tại mỗi biến và đưa ra khái niệm về dây kéo tương ứng.
class (Bifunctor b, Bifunctor (D b X), Bifunctor (D b Y)) => Diff2 b where
type D b (v :: Var) :: * -> * -> *
up :: Vary v -> Z b v x y -> b x y
down :: b x y -> b (Z b X x y) (Z b Y x y)
around :: Vary v -> Z b v x y -> Z b v (Z b X x y) (Z b Y x y)
data Z b v x y = (:<-) {cxZ :: D b v x y, elZ :: V v x y}
Thao D
tác này cần biết biến nào cần nhắm mục tiêu. Khóa kéo tương ứng Z b v
cho chúng ta biết biến nào v
phải được lấy nét. Khi chúng ta "trang trí với ngữ cảnh", chúng ta phải trang trí x
-elements với X
-contexts và y
-elements với Y
-contexts. Nhưng nếu không, đó là một câu chuyện tương tự.
Chúng tôi có hai nhiệm vụ còn lại: thứ nhất, chứng tỏ rằng bộ dụng cụ bifunctor của chúng tôi có thể phân biệt được; thứ hai, để hiển thị rằng Diff2 b
cho phép chúng tôi thiết lập Diff1 (Mu b)
.
Phân biệt bộ Bifunctor
Tôi e rằng điều này là khó hiểu hơn là gây dựng. Vui lòng bỏ qua.
Các hằng số như trước đây.
instance Diff2 (K a) where
type D (K a) v = K Void
up _ (K q :<- _) = absurd q
down (K a) = K a
around _ (K q :<- _) = absurd q
Nhân dịp này, tuổi thọ quá ngắn để phát triển lý thuyết về loại cấp độ Kronecker-delta, vì vậy tôi chỉ xử lý các biến một cách riêng biệt.
instance Diff2 (V X) where
type D (V X) X = K ()
type D (V X) Y = K Void
up VX (K () :<- XX x) = XX x
up VY (K q :<- _) = absurd q
down (XX x) = XX (K () :<- XX x)
around VX z@(K () :<- XX x) = K () :<- XX z
around VY (K q :<- _) = absurd q
instance Diff2 (V Y) where
type D (V Y) X = K Void
type D (V Y) Y = K ()
up VX (K q :<- _) = absurd q
up VY (K () :<- YY y) = YY y
down (YY y) = YY (K () :<- YY y)
around VX (K q :<- _) = absurd q
around VY z@(K () :<- YY y) = K () :<- YY z
Đối với các trường hợp cấu trúc, tôi thấy hữu ích khi giới thiệu một trình trợ giúp cho phép tôi xử lý các biến một cách đồng nhất.
vV :: Vary v -> Z b v x y -> V v (Z b X x y) (Z b Y x y)
vV VX z = XX z
vV VY z = YY z
Sau đó, tôi đã xây dựng các tiện ích để hỗ trợ loại "gắn thẻ lại" mà chúng tôi cần down
và around
. (Tất nhiên, tôi đã thấy những tiện ích nào tôi cần khi làm việc.)
zimap :: (Bifunctor c) => (forall v. Vary v -> D b v x y -> D b' v x y) ->
c (Z b X x y) (Z b Y x y) -> c (Z b' X x y) (Z b' Y x y)
zimap f = bimap
(\ (d :<- XX x) -> f VX d :<- XX x)
(\ (d :<- YY y) -> f VY d :<- YY y)
dzimap :: (Bifunctor (D c X), Bifunctor (D c Y)) =>
(forall v. Vary v -> D b v x y -> D b' v x y) ->
Vary v -> Z c v (Z b X x y) (Z b Y x y) -> D c v (Z b' X x y) (Z b' Y x y)
dzimap f VX (d :<- _) = bimap
(\ (d :<- XX x) -> f VX d :<- XX x)
(\ (d :<- YY y) -> f VY d :<- YY y)
d
dzimap f VY (d :<- _) = bimap
(\ (d :<- XX x) -> f VX d :<- XX x)
(\ (d :<- YY y) -> f VY d :<- YY y)
d
Và với lô đó đã sẵn sàng, chúng tôi có thể nghiền nát các chi tiết. Tổng rất dễ dàng.
instance (Diff2 b, Diff2 c) => Diff2 (b :++: c) where
type D (b :++: c) v = D b v :++: D c v
up v (L b' :<- vv) = L (up v (b' :<- vv))
down (L b) = L (zimap (const L) (down b))
down (R c) = R (zimap (const R) (down c))
around v z@(L b' :<- vv :: Z (b :++: c) v x y)
= L (dzimap (const L) v ba) :<- vV v z
where ba = around v (b' :<- vv :: Z b v x y)
around v z@(R c' :<- vv :: Z (b :++: c) v x y)
= R (dzimap (const R) v ca) :<- vV v z
where ca = around v (c' :<- vv :: Z c v x y)
Sản phẩm là công việc khó khăn, đó là lý do tại sao tôi là một nhà toán học hơn là một kỹ sư.
instance (Diff2 b, Diff2 c) => Diff2 (b :**: c) where
type D (b :**: c) v = (D b v :**: c) :++: (b :**: D c v)
up v (L (b' :**: c) :<- vv) = up v (b' :<- vv) :**: c
up v (R (b :**: c') :<- vv) = b :**: up v (c' :<- vv)
down (b :**: c) =
zimap (const (L . (:**: c))) (down b) :**: zimap (const (R . (b :**:))) (down c)
around v z@(L (b' :**: c) :<- vv :: Z (b :**: c) v x y)
= L (dzimap (const (L . (:**: c))) v ba :**:
zimap (const (R . (b :**:))) (down c))
:<- vV v z where
b = up v (b' :<- vv :: Z b v x y)
ba = around v (b' :<- vv :: Z b v x y)
around v z@(R (b :**: c') :<- vv :: Z (b :**: c) v x y)
= R (zimap (const (L . (:**: c))) (down b):**:
dzimap (const (R . (b :**:))) v ca)
:<- vV v z where
c = up v (c' :<- vv :: Z c v x y)
ca = around v (c' :<- vv :: Z c v x y)
Về mặt khái niệm, nó giống như trước đây, nhưng với nhiều quan liêu hơn. Tôi đã tạo ra những thứ này bằng công nghệ lỗ đánh máy trước, sử dụng undefined
như một sơ khai ở những nơi tôi chưa sẵn sàng làm việc và đưa ra một lỗi loại cố ý ở một nơi (tại bất kỳ thời điểm nào) mà tôi muốn một gợi ý hữu ích từ người đánh máy . Bạn cũng có thể có trải nghiệm đánh máy như trò chơi điện tử, ngay cả trong Haskell.
Khóa kéo phụ cho thùng chứa đệ quy
Đạo hàm riêng của b
liên quan đến việc X
cho chúng ta biết làm thế nào để tìm một nút con một bước bên trong một nút, vì vậy chúng ta có khái niệm thông thường về dây kéo.
data MuZpr b y = MuZpr
{ aboveMu :: [D b X (Mu b y) y]
, hereMu :: Mu b y
}
Chúng ta có thể phóng to đến tận gốc bằng cách cắm nhiều lần vào X
các vị trí.
muUp :: Diff2 b => MuZpr b y -> Mu b y
muUp (MuZpr {aboveMu = [], hereMu = t}) = t
muUp (MuZpr {aboveMu = (dX : dXs), hereMu = t}) =
muUp (MuZpr {aboveMu = dXs, hereMu = In (up VX (dX :<- XX t))})
Nhưng chúng ta cần phần tử -zippers.
Khóa kéo phần tử cho các điểm cố định của bộ phân đôi
Mỗi phần tử ở đâu đó bên trong một nút. Nút đó nằm dưới một chồng- X
dẫn xuất. Nhưng vị trí của phần tử trong nút đó được cho bởi một Y
đạo hàm. Chúng tôi nhận được
data MuCx b y = MuCx
{ aboveY :: [D b X (Mu b y) y]
, belowY :: D b Y (Mu b y) y
}
instance Diff2 b => Functor (MuCx b) where
fmap f (MuCx { aboveY = dXs, belowY = dY }) = MuCx
{ aboveY = map (bimap (fmap f) f) dXs
, belowY = bimap (fmap f) f dY
}
Mạnh dạn, tôi khẳng định
instance Diff2 b => Diff1 (Mu b) where
type DF (Mu b) = MuCx b
nhưng trước khi tôi phát triển các hoạt động, tôi sẽ cần một số bit và mảnh.
Tôi có thể trao đổi dữ liệu giữa dây khóa kéo và dây khóa kéo bifunctor như sau:
zAboveY :: ZF (Mu b) y -> [D b X (Mu b y) y]
zAboveY (d :<-: y) = aboveY d
zZipY :: ZF (Mu b) y -> Z b Y (Mu b y) y
zZipY (d :<-: y) = belowY d :<- YY y
Điều đó đủ để tôi định nghĩa:
upF z = muUp (MuZpr {aboveMu = zAboveY z, hereMu = In (up VY (zZipY z))})
Đó là, trước tiên chúng ta đi lên bằng cách ráp lại nút nơi có phần tử, biến một phần tử-dây kéo thành một khóa kéo phụ, sau đó thu phóng hết cỡ, như trên.
Tiếp theo, tôi nói
downF = yOnDown []
để đi xuống bắt đầu với ngăn xếp trống và xác định hàm trợ giúp đi down
lặp lại từ bên dưới bất kỳ ngăn xếp nào:
yOnDown :: Diff2 b => [D b X (Mu b y) y] -> Mu b y -> Mu b (ZF (Mu b) y)
yOnDown dXs (In b) = In (contextualize dXs (down b))
Bây giờ, down b
chỉ đưa chúng ta vào bên trong nút. Các khóa kéo chúng ta cần cũng phải mang ngữ cảnh của nút. Đó là những gì contextualise
làm:
contextualize :: (Bifunctor c, Diff2 b) =>
[D b X (Mu b y) y] ->
c (Z b X (Mu b y) y) (Z b Y (Mu b y) y) ->
c (Mu b (ZF (Mu b) y)) (ZF (Mu b) y)
contextualize dXs = bimap
(\ (dX :<- XX t) -> yOnDown (dX : dXs) t)
(\ (dY :<- YY y) -> MuCx {aboveY = dXs, belowY = dY} :<-: y)
Đối với mọi Y
-position, chúng ta phải cung cấp một phần tử-zipper, vì vậy tốt là chúng ta biết toàn bộ ngữ cảnh dXs
trở về gốc, cũng như bối cảnh dY
mô tả cách phần tử nằm trong nút của nó. Đối với mỗi- X
vị trí, có một cây con khác để khám phá, vì vậy chúng tôi phát triển ngăn xếp và tiếp tục!
Điều đó chỉ còn lại việc kinh doanh chuyển trọng tâm. Chúng ta có thể ở yên, hoặc đi xuống từ vị trí của chúng ta, hoặc đi lên, hoặc đi lên rồi đi xuống một con đường khác. Đây rồi.
aroundF z@(MuCx {aboveY = dXs, belowY = dY} :<-: _) = MuCx
{ aboveY = yOnUp dXs (In (up VY (zZipY z)))
, belowY = contextualize dXs (cxZ $ around VY (zZipY z))
} :<-: z
Như mọi khi, phần tử hiện có được thay thế bằng toàn bộ dây kéo của nó. Về belowY
phần, chúng tôi xem xét nơi khác mà chúng tôi có thể đi trong nút hiện có: chúng tôi sẽ tìm thấy phần tử thay thế -positions Y
hoặc- X
subnode khác để khám phá, vì vậy chúng tôi tìm contextualise
chúng. Về aboveY
phần mình, chúng ta phải làm việc theo cách của chúng ta để sao lưu chồng- X
dẫn xuất sau khi tập hợp lại nút mà chúng ta đã truy cập.
yOnUp :: Diff2 b => [D b X (Mu b y) y] -> Mu b y ->
[D b X (Mu b (ZF (Mu b) y)) (ZF (Mu b) y)]
yOnUp [] t = []
yOnUp (dX : dXs) (t :: Mu b y)
= contextualize dXs (cxZ $ around VX (dX :<- XX t))
: yOnUp dXs (In (up VX (dX :<- XX t)))
Ở mỗi bước của con đường, chúng ta có thể rẽ sang một nơi khác around
, hoặc tiếp tục đi lên.
Và đó là nó! Tôi chưa đưa ra bằng chứng chính thức về luật, nhưng đối với tôi, tôi thấy như thể các thao tác cẩn thận duy trì ngữ cảnh một cách chính xác khi chúng thu thập cấu trúc.
Chúng ta đã học được gì?
Khả năng khác biệt tạo ra khái niệm về sự vật trong ngữ cảnh của nó, tạo ra một cấu trúc hài hòa nơi extract
cung cấp cho bạn sự vật và duplicate
khám phá bối cảnh tìm kiếm những thứ khác để bối cảnh hóa. Nếu chúng ta có cấu trúc vi phân thích hợp cho các nút, chúng ta có thể phát triển cấu trúc vi phân cho toàn bộ cây.
Ồ, và việc đối xử với từng trình tạo kiểu riêng biệt là một điều kinh khủng trắng trợn. Cách tốt hơn là làm việc với các chức năng giữa các nhóm được lập chỉ mục
f :: (i -> *) -> (o -> *)
nơi chúng tôi tạo ra o
các loại cấu trúc i
khác nhau lưu trữ các loại phần tử khác nhau. Chúng được đóng lại dưới sự xây dựng của Jacobian
J f :: (i -> *) -> ((o, i) -> *)
trong đó mỗi (o, i)
-cấu trúc kết quả là một đạo hàm riêng, cho bạn biết cách tạo một i
lỗ trống trong một- o
cấu trúc. Nhưng đó là niềm vui được đánh máy phụ thuộc vào một thời gian khác.