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 Tree
temsil aslında olduğu Our Graph
lazı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 LFix
ve 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 ListF
tü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 OrdinaryList
bir ö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 ListF
bir ekstra tip parametresi ( next
) almasıdır.
Şimdi, ListF
aş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
List
ve[]
- Sen geri çevrilebilir ileri geri dönüştürebilir arasında
CoList
ve[]
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 Graph
tipin 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 Graph
biz yerine, özellikle x
ile node
:
data Graph a = forall node . MakeGraph node (node -> { id :: a, neighbors :: [ node ] })
Ancak, hala son bir zor kısım var, bu ExistentialQuantification
Haskell'den Dhall'a nasıl çevrilecek. forall
Aş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.