ใช่คุณสามารถสร้างรูปแบบกราฟปลอดภัยประเภทกำกับอาจเป็นวงจรใน Dhall เช่นนี้:
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" ]
การเป็นตัวแทนนี้รับประกันว่าไม่มีขอบหัก
ฉันยังเปลี่ยนคำตอบนี้เป็นแพ็คเกจที่คุณสามารถใช้:
แก้ไข:นี่คือแหล่งข้อมูลที่เกี่ยวข้องและคำอธิบายเพิ่มเติมที่สามารถช่วยส่องสว่างสิ่งที่เกิดขึ้น:
ก่อนอื่นให้เริ่มจากประเภท Haskell ต่อไปนี้สำหรับทรี :
data Tree a = Node { id :: a, neighbors :: [ Tree a ] }
คุณสามารถคิดว่าประเภทนี้เป็นโครงสร้างข้อมูลที่ขี้เกียจและอาจไม่มีที่สิ้นสุดซึ่งแสดงสิ่งที่คุณจะได้รับหากคุณเพิ่งไปเยี่ยมเพื่อนบ้าน
ทีนี้สมมติว่าการTree
เป็นตัวแทนข้างต้นเป็นของเราGraph
โดยเพียงแค่เปลี่ยนชื่อประเภทข้อมูลเป็นGraph
:
data Graph a = Node { id :: a, neighbors :: [ Graph a ] }
... แต่ถึงแม้ว่าเราต้องการที่จะใช้รูปแบบนี้เราก็ไม่มีวิธีที่จะจำลองแบบนั้นใน Dhall โดยตรงเพราะภาษา Dhall ไม่ได้ให้การสนับสนุนในตัวสำหรับโครงสร้างข้อมูลแบบเรียกซ้ำ ดังนั้นสิ่งที่เราจะทำ?
โชคดีที่มีวิธีฝังโครงสร้างข้อมูลแบบเรียกซ้ำและฟังก์ชันแบบเรียกซ้ำในภาษาที่ไม่ใช่แบบเรียกซ้ำเช่น Dhall ในความเป็นจริงมีสองวิธี!
สิ่งแรกที่ฉันอ่านที่แนะนำให้ฉันรู้จักกับเคล็ดลับนี้คือโพสต์ฉบับร่างโดย Wadler:
... แต่ฉันสามารถสรุปแนวคิดพื้นฐานโดยใช้ Haskell สองประเภทต่อไปนี้:
{-# LANGUAGE RankNTypes #-}
-- LFix is short for "Least fixed point"
newtype LFix f = LFix (forall x . (f x -> x) -> x)
... และ:
{-# LANGUAGE ExistentialQuantification #-}
-- GFix is short for "Greatest fixed point"
data GFix f = forall x . GFix x (x -> f x)
วิธีLFix
และการGFix
ทำงานคือคุณสามารถให้ "ชั้นหนึ่ง" ของ recursive ที่คุณต้องการหรือ "corecursive" (เช่นf
) และจากนั้นพวกเขาก็ให้บางอย่างที่ทรงพลังเท่ากับชนิดที่ต้องการโดยไม่ต้องการการสนับสนุนภาษาสำหรับการเรียกซ้ำหรือการตรวจสอบ .
ลองใช้รายการเป็นตัวอย่าง เราสามารถสร้างโมเดล "หนึ่งเลเยอร์" ของรายการโดยใช้ListF
ประเภทต่อไปนี้:
-- `ListF` is short for "List functor"
data ListF a next = Nil | Cons a next
เปรียบเทียบคำจำกัดความนั้นกับวิธีที่เราปกติจะOrdinaryList
ใช้คำนิยามประเภทข้อมูลแบบเรียกซ้ำโดยทั่วไป:
data OrdinaryList a = Nil | Cons a (OrdinaryList a)
ความแตกต่างหลักคือListF
ใช้พารามิเตอร์ประเภทพิเศษหนึ่งรายการ ( next
) ซึ่งเราใช้เป็นตัวยึดตำแหน่งสำหรับการเกิดซ้ำ / การกัดกร่อนทั้งหมดของประเภท
ตอนนี้มาพร้อมกับListF
เราสามารถกำหนดรายการแบบเรียกซ้ำและแบบวนซ้ำดังนี้:
type List a = LFix (ListF a)
type CoList a = GFix (ListF a)
... ที่ไหน:
List
เป็นรายการเรียกซ้ำที่ใช้งานโดยไม่สนับสนุนภาษาสำหรับการสอบถามซ้ำ
CoList
เป็นรายการตรวจสอบความถูกต้องที่นำมาใช้โดยไม่สนับสนุนภาษาสำหรับการตรวจสอบข้อมูล
ทั้งสองประเภทนี้เทียบเท่ากับ ("isomorphic to") []
ซึ่งหมายความว่า:
- คุณสามารถแปลงกลับไปกลับมาระหว่าง
List
และ[]
- คุณสามารถแปลงกลับไปกลับมาระหว่าง
CoList
และ[]
มาพิสูจน์กันโดยกำหนดฟังก์ชั่นการแปลงเหล่านั้น!
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
ดังนั้นขั้นตอนแรกในการนำ Dhall ไปใช้คือการแปลงGraph
ชนิดเรียกซ้ำ:
data Graph a = Node { id :: a, neighbors :: [ Graph a ] }
... ถึงการนำเสนอร่วมแบบเรียกซ้ำ:
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)
... แม้ว่าจะทำให้ประเภทง่ายขึ้นเล็กน้อยฉันพบว่าง่ายกว่าGFix
ในกรณีที่f = GraphF
:
data GraphF a next = Node { id ::: a, neighbors :: [ next ] }
data Graph a = forall x . Graph x (x -> GraphF a x)
Haskell ไม่มีบันทึกที่ไม่ระบุชื่อเช่น Dhall แต่ถ้าเป็นเช่นนั้นเราสามารถทำให้ประเภทนั้นง่ายขึ้นโดยการนิยามคำจำกัดความของGraphF
:
data Graph a = forall x . MakeGraph x (x -> { id :: a, neighbors :: [ x ] })
ตอนนี้สิ่งนี้จะเริ่มดูเหมือน Dhall สำหรับ a Graph
โดยเฉพาะถ้าเราแทนที่x
ด้วยnode
:
data Graph a = forall node . MakeGraph node (node -> { id :: a, neighbors :: [ node ] })
อย่างไรก็ตามยังมีอีกหนึ่งส่วนสุดท้ายที่ยุ่งยากซึ่งเป็นวิธีการแปลExistentialQuantification
จาก Haskell เป็น Dhall ปรากฎว่าคุณสามารถแปลปริมาณการดำรงอยู่เป็นปริมาณสากล (เช่นforall
) โดยใช้การเทียบเท่าดังต่อไปนี้:
exists y . f y ≅ forall x . (forall y . f y -> x) -> x
ฉันเชื่อว่าสิ่งนี้เรียกว่า "skolemization"
สำหรับรายละเอียดเพิ่มเติมดู:
... และเคล็ดลับสุดท้ายนั้นให้ประเภท Dhall:
let Graph
: Type
= forall (Graph : Type)
-> forall ( MakeGraph
: forall (Node : Type)
-> Node
-> (Node -> { id : Text, neighbors : List Node })
-> Graph
)
-> Graph
... โดยที่forall (Graph : Type)
มีบทบาทเช่นเดียวกับforall x
ในสูตรก่อนหน้าและforall (Node : Type)
มีบทบาทเช่นเดียวกับforall y
ในสูตรก่อนหน้า