Có, bạn có thể mô hình một biểu đồ - an toàn, có hướng, có thể theo chu kỳ, trong Dhall, như thế này:
let List/map =
https://prelude.dhall-lang.org/v14.0.0/List/map sha256:dd845ffb4568d40327f2a817eb42d1c6138b929ca758d50bc33112ef3c885680
let Graph
: Type
= forall (Graph : Type)
-> forall ( MakeGraph
: forall (Node : Type)
-> Node
-> (Node -> { id : Text, neighbors : List Node })
-> Graph
)
-> Graph
let MakeGraph
: forall (Node : Type)
-> Node
-> (Node -> { id : Text, neighbors : List Node })
-> Graph
= \(Node : Type)
-> \(current : Node)
-> \(step : Node -> { id : Text, neighbors : List Node })
-> \(Graph : Type)
-> \ ( MakeGraph
: forall (Node : Type)
-> Node
-> (Node -> { id : Text, neighbors : List Node })
-> Graph
)
-> MakeGraph Node current step
let -- Get `Text` label for the current node of a Graph
id
: Graph -> Text
= \(graph : Graph)
-> graph
Text
( \(Node : Type)
-> \(current : Node)
-> \(step : Node -> { id : Text, neighbors : List Node })
-> (step current).id
)
let -- Get all neighbors of the current node
neighbors
: Graph -> List Graph
= \(graph : Graph)
-> graph
(List Graph)
( \(Node : Type)
-> \(current : Node)
-> \(step : Node -> { id : Text, neighbors : List Node })
-> let neighborNodes
: List Node
= (step current).neighbors
let nodeToGraph
: Node -> Graph
= \(node : Node)
-> \(Graph : Type)
-> \ ( MakeGraph
: forall (Node : Type)
-> forall (current : Node)
-> forall ( step
: Node
-> { id : Text
, neighbors : List Node
}
)
-> Graph
)
-> MakeGraph Node node step
in List/map Node Graph nodeToGraph neighborNodes
)
let {- Example node type for a graph with three nodes
For your Wiki, replace this with a type with one alternative per document
-}
Node =
< Node0 | Node1 | Node2 >
let {- Example graph with the following nodes and edges between them:
Node0 ↔ Node1
↓
Node2
↺
The starting node is Node0
-}
example
: Graph
= let step =
\(node : Node)
-> merge
{ Node0 = { id = "0", neighbors = [ Node.Node1, Node.Node2 ] }
, Node1 = { id = "1", neighbors = [ Node.Node0 ] }
, Node2 = { id = "2", neighbors = [ Node.Node2 ] }
}
node
in MakeGraph Node Node.Node0 step
in assert : List/map Graph Text id (neighbors example) === [ "1", "2" ]
Đại diện này đảm bảo sự vắng mặt của các cạnh bị hỏng.
Tôi cũng đã biến câu trả lời này thành một gói bạn có thể sử dụng:
Chỉnh sửa: Dưới đây là các tài nguyên có liên quan và giải thích bổ sung có thể giúp làm sáng tỏ những gì đang diễn ra:
Đầu tiên, bắt đầu từ loại Haskell sau cho cây :
data Tree a = Node { id :: a, neighbors :: [ Tree a ] }
Bạn có thể nghĩ loại này là một cấu trúc dữ liệu lười biếng và có khả năng vô hạn đại diện cho những gì bạn sẽ nhận được nếu bạn tiếp tục truy cập hàng xóm.
Bây giờ, hãy giả vờ rằng Tree
đại diện trên thực sự là của chúng tôi Graph
bằng cách đổi tên kiểu dữ liệu thành Graph
:
data Graph a = Node { id :: a, neighbors :: [ Graph a ] }
... nhưng ngay cả khi chúng tôi muốn sử dụng loại này, chúng tôi không có cách để mô hình trực tiếp loại đó trong Dhall vì ngôn ngữ Dhall không cung cấp hỗ trợ tích hợp cho các cấu trúc dữ liệu đệ quy. Vậy ta phải làm sao?
May mắn thay, thực sự có một cách để nhúng các cấu trúc dữ liệu đệ quy và các hàm đệ quy trong một ngôn ngữ không đệ quy như Dhall. Trong thực tế, có hai cách!
Điều đầu tiên tôi đọc đã giới thiệu cho tôi về thủ thuật này là bài dự thảo sau đây của Wadler:
... nhưng tôi có thể tóm tắt ý tưởng cơ bản bằng hai loại Haskell sau:
{-# LANGUAGE RankNTypes #-}
-- LFix is short for "Least fixed point"
newtype LFix f = LFix (forall x . (f x -> x) -> x)
... và:
{-# LANGUAGE ExistentialQuantification #-}
-- GFix is short for "Greatest fixed point"
data GFix f = forall x . GFix x (x -> f x)
Cách thức LFix
và GFix
công việc là bạn có thể cung cấp cho họ "một lớp" loại đệ quy mong muốn hoặc "corecursive" (nghĩa là f
) và sau đó họ cung cấp cho bạn thứ gì đó mạnh mẽ như loại mong muốn mà không cần hỗ trợ ngôn ngữ cho đệ quy hoặc corecursion .
Hãy sử dụng danh sách làm ví dụ. Chúng ta có thể mô hình hóa "một lớp" của danh sách bằng cách sử dụng ListF
loại sau :
-- `ListF` is short for "List functor"
data ListF a next = Nil | Cons a next
So sánh định nghĩa đó với cách chúng ta thường định nghĩa OrdinaryList
bằng cách sử dụng định nghĩa kiểu dữ liệu đệ quy thông thường:
data OrdinaryList a = Nil | Cons a (OrdinaryList a)
Sự khác biệt chính là ListF
có thêm một tham số loại ( next
), mà chúng tôi sử dụng như một trình giữ chỗ cho tất cả các lần xuất hiện đệ quy / corecursive của loại.
Bây giờ, được trang bị ListF
, chúng ta có thể định nghĩa các danh sách đệ quy và corecursive như thế này:
type List a = LFix (ListF a)
type CoList a = GFix (ListF a)
... Ở đâu:
List
là một danh sách đệ quy được thực hiện mà không cần hỗ trợ ngôn ngữ cho đệ quy
CoList
là một danh sách corecursive được thực hiện mà không cần hỗ trợ ngôn ngữ cho corecursion
Cả hai loại này đều tương đương với ("đẳng cấu với") []
, có nghĩa là:
- Bạn có thể chuyển đổi qua lại giữa
List
và[]
- Bạn có thể chuyển đổi qua lại giữa
CoList
và[]
Hãy chứng minh rằng bằng cách xác định các hàm chuyển đổi đó!
fromList :: List a -> [a]
fromList (LFix f) = f adapt
where
adapt (Cons a next) = a : next
adapt Nil = []
toList :: [a] -> List a
toList xs = LFix (\k -> foldr (\a x -> k (Cons a x)) (k Nil) xs)
fromCoList :: CoList a -> [a]
fromCoList (GFix start step) = loop start
where
loop state = case step state of
Nil -> []
Cons a state' -> a : loop state'
toCoList :: [a] -> CoList a
toCoList xs = GFix xs step
where
step [] = Nil
step (y : ys) = Cons y ys
Vì vậy, bước đầu tiên trong việc thực hiện loại Dhall là chuyển đổi Graph
loại đệ quy :
data Graph a = Node { id :: a, neighbors :: [ Graph a ] }
... đến đại diện đồng quy tương đương:
data GraphF a next = Node { id ::: a, neighbors :: [ next ] }
data GFix f = forall x . GFix x (x -> f x)
type Graph a = GFix (GraphF a)
... mặc dù để đơn giản hóa các loại một chút, tôi thấy việc chuyên môn hóa dễ dàng hơn GFix
trong trường hợp f = GraphF
:
data GraphF a next = Node { id ::: a, neighbors :: [ next ] }
data Graph a = forall x . Graph x (x -> GraphF a x)
Haskell không có hồ sơ ẩn danh như Dhall, nhưng nếu có thì chúng ta có thể đơn giản hóa loại này hơn nữa bằng cách đưa ra định nghĩa về GraphF
:
data Graph a = forall x . MakeGraph x (x -> { id :: a, neighbors :: [ x ] })
Bây giờ, điều này đang bắt đầu giống như kiểu Dhall cho a Graph
, đặc biệt nếu chúng ta thay thế x
bằng node
:
data Graph a = forall node . MakeGraph node (node -> { id :: a, neighbors :: [ node ] })
Tuy nhiên, vẫn còn một phần khó khăn cuối cùng, đó là làm thế nào để dịch ExistentialQuantification
từ Haskell sang Dhall. Nó chỉ ra rằng bạn luôn có thể dịch lượng hóa hiện sinh sang định lượng phổ (nghĩa là forall
) bằng cách sử dụng tương đương sau:
exists y . f y ≅ forall x . (forall y . f y -> x) -> x
Tôi tin rằng điều này được gọi là "skolemization"
Để biết thêm chi tiết, xem:
... Và thủ thuật cuối cùng đó cung cấp cho bạn loại Dhall:
let Graph
: Type
= forall (Graph : Type)
-> forall ( MakeGraph
: forall (Node : Type)
-> Node
-> (Node -> { id : Text, neighbors : List Node })
-> Graph
)
-> Graph
... Trong đó forall (Graph : Type)
đóng vai trò giống như forall x
trong công thức trước và forall (Node : Type)
đóng vai trò tương tự như forall y
trong công thức trước.