Dhall'da geçerli grafik türü kodlanabilir mi?


10

Dhall'da bir wiki'yi (yönlendirilmiş grafik içeren bir belge kümesi) temsil etmek istiyorum. Bu belgeler HTML biçiminde oluşturulacak ve bozuk bağlantıların oluşturulmasını önlemek istiyorum. Gördüğüm gibi, bu, geçersiz grafikler (var olmayan düğümlere bağlantıları olan grafikler) tür sistemi üzerinden temsil edilemez hale getirerek veya olası bir grafikteki hataların listesini döndürmek için bir işlev yazarak (örn. X, Düğüm A, var olmayan Düğüm B'ye bir bağlantı içeriyor ").

Saf bir bitişiklik listesi temsili şöyle görünebilir:

let Node : Type = {
    id: Text,
    neighbors: List Text
}
let Graph : Type = List Node
let example : Graph = [
    { id = "a", neighbors = ["b"] }
]
in example

Bu örnek açıkça görüleceği gibi, bu tür geçerli grafiklere karşılık gelmeyen değerleri kabul eder ("b" kimliğine sahip bir düğüm yoktur, ancak "a" kimliğine sahip düğüm "b" kimliğine sahip bir komşuyu şart koşar). Dahası, her düğümün komşularının üzerine katlanarak bu sorunların bir listesini oluşturmak mümkün değildir, çünkü Dhall tasarımla dize karşılaştırmasını desteklemez.

Kırık bağlantılar listesinin hesaplanmasına veya tür sistemi aracılığıyla bozuk bağlantıların hariç tutulmasına izin verecek herhangi bir gösterim var mı?

GÜNCELLEME: Dhall'da Naturals'ın karşılaştırılabilir olduğunu keşfettim. Yani herhangi bir geçersiz kenarları ("kırık bağlantılar") tanımlamak için bir fonksiyon yazılabilir ve tanımlayıcıları Naturals ise bir tanımlayıcının yinelenen kullanımları varsayalım.

Bununla birlikte, bir Grafik türünün tanımlanıp tanımlanamayacağı konusunda asıl soru kalır.


Grafiği bunun yerine kenar listesi olarak gösterin. Düğümler mevcut kenarlardan çıkarılabilir. Her kenar bir kaynak düğümü ve bir hedef düğümü içerir, ancak bağlantısı kesilmiş düğümleri barındırmak için hedef isteğe bağlı olabilir.
chepner

Yanıtlar:


18

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!

  • F-cebirleri - Özyineleme uygulamak için kullanılır
  • F-coalgebras - "Oluk giderme" uygulamak için kullanılır

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.


1
Bu cevap için ve Dhall'ı geliştirmek için gereken tüm sıkı çalışma için çok teşekkür ederim! Dhall / System F'ye yeni gelenlerin burada ne yaptığınızı daha iyi anlamak için okuyabilecekleri herhangi bir öneride bulunabilir misiniz, başka hangi grafik gösterimleri olabilir? Burada yaptığınız şeyi, Grafiğinizin herhangi bir değerinden bir derinlik ilk aramasıyla bitişiklik listesi temsilini üretebilecek bir işlev yazmak için genişletmek istiyorum.
Bjørn Westergard

4
@ BjørnWestergard: Rica ederim! Cevabımı, arkasındaki teoriyi açıklamak için, faydalı referanslar da dahil olmak üzere düzenledim
Gabriel Gonzalez
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.