Evet, Dhall'da tür güvenli, yönlendirilmiş, muhtemelen döngüsel bir grafik modelleyebilirsiniz:
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" ]
Bu gösterim kırık kenarların olmamasını garanti eder.
Bu yanıtı kullanabileceğiniz bir pakete de çevirdim:
Düzenleme: Neler olup bittiğini aydınlatmaya yardımcı olabilecek ilgili kaynaklar ve ek açıklamalar şunlardır:
İlk olarak, bir ağaç için aşağıdaki Haskell tipinden başlayın :
data Tree a = Node { id :: a, neighbors :: [ Tree a ] }
Bu türü, sadece komşuları ziyaret etmeye devam ederseniz alacağınızı temsil eden tembel ve potansiyel olarak sonsuz bir veri yapısı olarak düşünebilirsiniz.
Şimdi, yukarıdaki o farz edelim Treetemsil aslında olduğu Our Graphlazım veri türünü yeniden adlandırarak Graph:
data Graph a = Node { id :: a, neighbors :: [ Graph a ] }
... ancak bu türü kullanmak istesek bile, bu türü doğrudan Dhall'da modellemenin bir yolu yoktur, çünkü Dhall dili özyinelemeli veri yapıları için yerleşik destek sağlamaz. Peki ne yapıyoruz?
Neyse ki, özyinelemeli veri yapılarını ve özyinelemeli fonksiyonları Dhall gibi özyinelemesiz bir dilde yerleştirmenin bir yolu vardır. Aslında, iki yol var!
Beni bu numaraya tanıtan ilk okuduğum şey Wadler'ın şu taslak yazısıydı:
... ancak aşağıdaki iki Haskell türünü kullanarak temel fikri özetleyebilirim:
{-# LANGUAGE RankNTypes #-}
-- LFix is short for "Least fixed point"
newtype LFix f = LFix (forall x . (f x -> x) -> x)
... ve:
{-# LANGUAGE ExistentialQuantification #-}
-- GFix is short for "Greatest fixed point"
data GFix f = forall x . GFix x (x -> f x)
Bu LFixve GFixçalışma şekli, istediğiniz özyinelemeli veya "düzeltici" tipte (yani f) "bir katman" verebilmeniz ve daha sonra size özyineleme veya korecursiyon için dil desteği gerektirmeden istenen tür kadar güçlü bir şey verebilmenizdir. .
Örnek olarak listeleri kullanalım. Aşağıdaki ListFtürü kullanarak listenin "bir katmanını" modelleyebiliriz :
-- `ListF` is short for "List functor"
data ListF a next = Nil | Cons a next
Bu tanımı normal OrdinaryListbir özyinelemeli veri türü tanımı kullanarak normalde nasıl tanımlayacağımızla karşılaştırın :
data OrdinaryList a = Nil | Cons a (OrdinaryList a)
Temel fark, türün tüm yinelemeli / düzeltici oluşumları için yer tutucu olarak kullandığımız ListFbir ekstra tip parametresi ( next) almasıdır.
Şimdi, ListFaşağıdaki gibi, özyinelemeli ve düzeltici listeleri tanımlayabiliriz:
type List a = LFix (ListF a)
type CoList a = GFix (ListF a)
... nerede:
List özyineleme için dil desteği olmadan uygulanan özyinelemeli bir listedir
CoList corecursion dil desteği olmadan uygulanan korecursif bir listedir
Bu türlerin her ikisi de ("izomorfik" e) eşdeğerdir [], yani:
- Sen geri çevrilebilir ileri geri dönüştürebilir arasında
Listve[]
- Sen geri çevrilebilir ileri geri dönüştürebilir arasında
CoListve[]
Bu dönüşüm fonksiyonlarını tanımlayarak kanıtlayalım!
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 tipinin uygulanmasındaki ilk adım, özyinelemeli Graphtipin dönüştürülmesiydi :
data Graph a = Node { id :: a, neighbors :: [ Graph a ] }
... eşdeğer bir özyinelemeli gösterime:
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)
... tiplerini biraz basitleştirmek için GFix, şu durumlarda uzmanlaşmayı daha kolay buluyorum f = GraphF:
data GraphF a next = Node { id ::: a, neighbors :: [ next ] }
data Graph a = forall x . Graph x (x -> GraphF a x)
Haskell'in Dhall gibi anonim kayıtları yok, ancak eğer öyleyse, tanımını satır içine alarak türü daha da basitleştirebiliriz GraphF:
data Graph a = forall x . MakeGraph x (x -> { id :: a, neighbors :: [ x ] })
Şimdi bu için Dhall türü gibi görünmeye başlıyor Graphbiz yerine, özellikle xile node:
data Graph a = forall node . MakeGraph node (node -> { id :: a, neighbors :: [ node ] })
Ancak, hala son bir zor kısım var, bu ExistentialQuantificationHaskell'den Dhall'a nasıl çevrilecek. forallAşağıdaki eşdeğerliği kullanarak varoluşsal nicelemeyi her zaman evrensel nicelemeye (yani ) çevirebileceğiniz anlaşılmaktadır :
exists y . f y ≅ forall x . (forall y . f y -> x) -> x
Buna "skolemizasyon" denildiğine inanıyorum
Daha fazla ayrıntı için bkz:
... ve son numara size Dhall tipini veriyor:
let Graph
: Type
= forall (Graph : Type)
-> forall ( MakeGraph
: forall (Node : Type)
-> Node
-> (Node -> { id : Text, neighbors : List Node })
-> Graph
)
-> Graph
... nerede forall (Graph : Type)ile aynı rol oynar forall xönceki formüldeki ve forall (Node : Type)aynı rol oynar forall yönceki formülde.