Loại biểu đồ hợp lệ có thể được mã hóa trong Dhall không?


10

Tôi muốn trình bày một wiki (một bộ tài liệu bao gồm một biểu đồ có hướng) trong Dhall. Các tài liệu này sẽ được hiển thị thành HTML và tôi muốn ngăn các liên kết bị hỏng không được tạo. Như tôi thấy, điều này có thể được thực hiện bằng cách tạo các biểu đồ không hợp lệ (biểu đồ có liên kết đến các nút không tồn tại) không thể biểu thị thông qua hệ thống loại hoặc bằng cách viết một hàm để trả về danh sách lỗi trong bất kỳ biểu đồ nào có thể (ví dụ: "Trong biểu đồ có thể X, Nút A chứa liên kết đến Nút B không tồn tại ").

Một đại diện danh sách kề kề ngây thơ có thể trông giống như thế này:

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

Vì ví dụ này cho thấy rõ, loại này thừa nhận các giá trị không tương ứng với các biểu đồ hợp lệ (không có nút nào có id là "b", nhưng nút có id "a" quy định hàng xóm có id "b"). Hơn nữa, không thể tạo danh sách các vấn đề này bằng cách gấp lại các hàng xóm của mỗi Nút, bởi vì Dhall không hỗ trợ so sánh chuỗi theo thiết kế.

Có đại diện nào cho phép tính toán danh sách các liên kết bị hỏng hoặc loại trừ các liên kết bị hỏng thông qua hệ thống loại không?

CẬP NHẬT: Tôi vừa phát hiện ra rằng Naturals có thể so sánh được ở Dhall. Vì vậy, tôi cho rằng một chức năng có thể được viết để xác định bất kỳ cạnh không hợp lệ nào ("liên kết bị hỏng") và sử dụng trùng lặp định danh nếu số nhận dạng là Naturals.

Tuy nhiên, câu hỏi ban đầu về việc có thể xác định loại Biểu đồ hay không.


Thay vào đó biểu thị đồ thị dưới dạng danh sách các cạnh. Các nút có thể được suy ra từ các cạnh hiện có. Mỗi cạnh sẽ bao gồm một nút nguồn và một nút đích, nhưng để chứa các nút bị ngắt kết nối, đích có thể là tùy chọn.
chepner

Câu trả lời:


18

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ự của chúng tôi Graphbằ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!

  • F-algebras - Được sử dụng để thực hiện đệ quy
  • F-thangebras - Được sử dụng để thực hiện "corecursion"

Đ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 LFixGFixcô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 ListFloạ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 OrdinaryListbằ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à ListFcó 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[]
  • Bạn có thể chuyển đổi qua lại giữa CoList[]

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 Graphloạ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 GFixtrong 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ế xbằ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 ExistentialQuantificationtừ 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 xtrong công thức trước và forall (Node : Type)đóng vai trò tương tự như forall ytrong công thức trước.


1
Cảm ơn bạn rất nhiều vì câu trả lời này, và cho tất cả những công việc khó khăn cần thiết để phát triển Dhall! Bạn có thể đề xuất bất kỳ người mới sử dụng tài liệu nào cho Dhall / System F có thể đọc để hiểu rõ hơn những gì bạn đã làm ở đây không, những biểu diễn đồ thị nào khác có thể có? Tôi muốn có thể mở rộng những gì bạn đã thực hiện ở đây để viết một hàm có thể tạo ra biểu diễn danh sách kề từ bất kỳ giá trị nào của loại Biểu đồ của bạn thông qua tìm kiếm đầu tiên chuyên sâu.
Bjørn Westergard

4
@ BjørnWestergard: Bạn được chào đón! Tôi đã chỉnh sửa câu trả lời của mình để giải thích lý thuyết đằng sau nó, bao gồm các tài liệu tham khảo hữu ích
Gabriel Gonzalez
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.