implica 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of implica might be problematic. Click here for more details.

implica/__init__.py ADDED
@@ -0,0 +1,44 @@
1
+ """
2
+ Guanciale - Implicational Logic Graph Library
3
+
4
+ A Python package for working with graphs representing minimal implicational
5
+ logic models. This library provides tools for constructing and manipulating
6
+ type systems based on combinatory logic, with support for transactional
7
+ graph operations.
8
+
9
+ Import as 'gil' for a clean, concise API:
10
+ import guanciale as gil
11
+
12
+ graph = gil.Graph()
13
+ A = gil.var("A")
14
+ B = gil.var("B")
15
+
16
+ with graph.connect() as conn:
17
+ conn.add_node(gil.node(A))
18
+ conn.add_node(gil.node(B))
19
+ """
20
+
21
+ from .core import *
22
+ from .graph import Node, Edge, node, edge, Graph, Connection
23
+ from . import mutations
24
+
25
+
26
+ __all__ = [
27
+ "BaseType",
28
+ "Variable",
29
+ "Application",
30
+ "var",
31
+ "app",
32
+ "Combinator",
33
+ "S",
34
+ "K",
35
+ "Node",
36
+ "Edge",
37
+ "node",
38
+ "edge",
39
+ "Graph",
40
+ "Connection",
41
+ "mutations",
42
+ ]
43
+
44
+ __version__ = "0.3.0"
@@ -0,0 +1,4 @@
1
+ from .types import BaseType, Variable, Application, var, app
2
+ from .combinator import Combinator, S, K
3
+
4
+ __all__ = ["BaseType", "Variable", "Application", "var", "app", "Combinator", "S", "K"]
@@ -0,0 +1,149 @@
1
+ """
2
+ Combinator system for minimal implicational logic.
3
+
4
+ This module defines combinators that represent transformations between types
5
+ in the implicational logic graph.
6
+ """
7
+
8
+ import hashlib
9
+ from functools import cached_property
10
+
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+
13
+ from .types import BaseType, Application, app
14
+
15
+
16
+ class Combinator(BaseModel):
17
+ """
18
+ A combinator with a specific type.
19
+
20
+ In combinatory logic:
21
+ - S combinator: S x y z = x z (y z) with type (A -> B -> C) -> (A -> B) -> A -> C
22
+ - K combinator: K x y = x with type A -> B -> A
23
+
24
+ Each combinator has a name (e.g., "S", "K") and an application type.
25
+ """
26
+
27
+ name: str = Field(..., min_length=1, description="Name of the combinator")
28
+ type: BaseType = Field(..., description="Type of the combinator")
29
+
30
+ model_config = ConfigDict(frozen=True) # Make immutable
31
+
32
+ @cached_property
33
+ def uid(self) -> str:
34
+ """
35
+ Return a unique identifier for this combinator.
36
+
37
+ The uid includes both the name and the type, as combinators with the same
38
+ name but different types are different combinators.
39
+
40
+ Returns:
41
+ str: SHA256 hash of the combinator name and type
42
+ """
43
+ content = f"Comb({self.name},{self.type.uid})"
44
+ return hashlib.sha256(content.encode("utf-8")).hexdigest()
45
+
46
+ def __str__(self) -> str:
47
+ """Return the combinator name and type."""
48
+ return f"{self.name}: {self.type}"
49
+
50
+ def __repr__(self) -> str:
51
+ """Return a detailed representation."""
52
+ return f"Combinator(name='{self.name}', type={self.type})"
53
+
54
+ def __hash__(self) -> int:
55
+ """Make combinator hashable for use in sets and dicts."""
56
+ return hash((self.name, self.type.uid))
57
+
58
+ def __call__(self, other: "Combinator") -> "Combinator":
59
+ """
60
+ Apply this combinator to another combinator.
61
+
62
+ If this combinator has type A -> B and the input has type A,
63
+ the result will be a combinator with type B.
64
+
65
+ Args:
66
+ other: The combinator to apply this combinator to
67
+
68
+ Returns:
69
+ Combinator: The result of the application
70
+
71
+ Raises:
72
+ TypeError: If the types are incompatible
73
+ """
74
+ if not isinstance(self.type, Application):
75
+ raise TypeError(
76
+ f"Cannot apply combinator {self.name} with type {self.type} "
77
+ f"(not a function type)"
78
+ )
79
+
80
+ # Check if the input type matches
81
+ if self.type.input_type.uid != other.type.uid:
82
+ raise TypeError(
83
+ f"Type mismatch: expected {self.type.input_type}, " f"got {other.type}"
84
+ )
85
+
86
+ # Create the result combinator
87
+ result_name = f"({self.name} {other.name})"
88
+ return Combinator(name=result_name, type=self.type.output_type)
89
+
90
+
91
+ # Helper functions for creating S and K combinators with specific types
92
+ def S(A: BaseType, B: BaseType, C: BaseType) -> Combinator:
93
+ """
94
+ Create the S combinator with specific types.
95
+
96
+ Type: (A -> B -> C) -> (A -> B) -> A -> C
97
+
98
+ The S combinator implements the following computation:
99
+ S x y z = x z (y z)
100
+
101
+ Args:
102
+ A: Type variable A
103
+ B: Type variable B
104
+ C: Type variable C
105
+
106
+ Returns:
107
+ Combinator: The S combinator with the specified types
108
+ """
109
+ s_type = app(
110
+ app(A, app(B, C)), # (A -> B -> C)
111
+ app(app(A, B), app(A, C)), # (A -> B) -> A -> C
112
+ )
113
+ return Combinator(name="S", type=s_type)
114
+
115
+
116
+ def K(A: BaseType, B: BaseType) -> Combinator:
117
+ """
118
+ Create the K combinator with specific types.
119
+
120
+ Type: A -> B -> A
121
+
122
+ The K combinator implements the following computation:
123
+ K x y = x
124
+
125
+ Args:
126
+ A: Type variable A
127
+ B: Type variable B
128
+
129
+ Returns:
130
+ Combinator: The K combinator with type A -> B -> A
131
+ """
132
+ k_type = app(A, app(B, A)) # A -> B -> A
133
+ return Combinator(name="K", type=k_type)
134
+
135
+
136
+ def combinator(name: str, type: BaseType) -> Combinator:
137
+ """
138
+ Helper function to create a combinator from a sequence string.
139
+
140
+ Args:
141
+ name: Sequence of 'S' and 'K' characters
142
+
143
+ Returns:
144
+ Combinator: A new Combinator instance
145
+
146
+ Raises:
147
+ ValueError: If the name contains invalid characters
148
+ """
149
+ return Combinator(name=name, type=type)
implica/core/types.py ADDED
@@ -0,0 +1,140 @@
1
+ """
2
+ Type system for minimal implicational logic.
3
+
4
+ This module defines the type hierarchy for the implicational logic graph:
5
+ - BaseType: Abstract base for all types
6
+ - Variable: Simple type variables (e.g., A, B, C)
7
+ - Application: Function application types (e.g., A -> B)
8
+ """
9
+
10
+ import hashlib
11
+ from abc import ABC, abstractmethod
12
+ from functools import cached_property
13
+
14
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
15
+
16
+
17
+ class BaseType(BaseModel, ABC):
18
+ """Abstract base class for all types in the implicational logic system."""
19
+
20
+ model_config = ConfigDict(frozen=True) # Make immutable
21
+
22
+ @cached_property
23
+ @abstractmethod
24
+ def uid(self) -> str:
25
+ """
26
+ Unique identifier for this type, computed as a hash.
27
+
28
+ This property is cached for performance and should uniquely identify
29
+ the type based on its structure.
30
+
31
+ Returns:
32
+ str: A unique string identifier for this type
33
+ """
34
+ pass
35
+
36
+ @abstractmethod
37
+ def __str__(self) -> str:
38
+ """String representation of the type."""
39
+ pass
40
+
41
+
42
+ class Variable(BaseType):
43
+ """
44
+ A simple type variable (e.g., A, B, C).
45
+
46
+ Variables represent atomic types in the type system.
47
+ """
48
+
49
+ name: str = Field(..., min_length=1, description="Name of the type variable")
50
+
51
+ @field_validator("name")
52
+ @classmethod
53
+ def validate_name(cls, v: str) -> str:
54
+ """Validate that the variable name is non-empty and contains valid characters."""
55
+ if not v or not v.strip():
56
+ raise ValueError("Variable name cannot be empty or whitespace")
57
+ return v.strip()
58
+
59
+ @cached_property
60
+ def uid(self) -> str:
61
+ """Return the variable name hashed as its unique identifier."""
62
+ content = f"Var({self.name})"
63
+ return hashlib.sha256(content.encode("utf-8")).hexdigest()
64
+
65
+ def __str__(self) -> str:
66
+ """Return the variable name."""
67
+ return self.name
68
+
69
+ def __repr__(self) -> str:
70
+ """Return a detailed representation."""
71
+ return f"Variable(name='{self.name}')"
72
+
73
+
74
+ class Application(BaseType):
75
+ """
76
+ A function application type (e.g., A -> B).
77
+
78
+ Represents the type of a function that takes `input_type` and returns `output_type`.
79
+ In notation: input_type -> output_type
80
+ """
81
+
82
+ input_type: BaseType = Field(..., description="Input type of the function")
83
+ output_type: BaseType = Field(..., description="Output type of the function")
84
+
85
+ @cached_property
86
+ def uid(self) -> str:
87
+ """
88
+ Compute unique identifier based on input and output types.
89
+
90
+ Returns:
91
+ str: A SHA256 hash of the structure
92
+ """
93
+ content = f"App({self.input_type.uid},{self.output_type.uid})"
94
+ return hashlib.sha256(content.encode("utf-8")).hexdigest()
95
+
96
+ def __str__(self) -> str:
97
+ """
98
+ Return a human-readable string representation.
99
+
100
+ Uses parentheses for nested applications to maintain clarity.
101
+ """
102
+ # Add parentheses around input if it's also an Application
103
+ if isinstance(self.input_type, Application):
104
+ input_str = f"({self.input_type})"
105
+ else:
106
+ input_str = str(self.input_type)
107
+
108
+ return f"{input_str} -> {self.output_type}"
109
+
110
+ def __repr__(self) -> str:
111
+ """Return a detailed representation."""
112
+ return f"Application(input_type={repr(self.input_type)}, output_type={repr(self.output_type)})"
113
+
114
+
115
+ # Helper function to create types more easily
116
+ def var(name: str) -> Variable:
117
+ """
118
+ Helper function to create a Variable.
119
+
120
+ Args:
121
+ name: Name of the variable
122
+
123
+ Returns:
124
+ Variable: A new Variable instance
125
+ """
126
+ return Variable(name=name)
127
+
128
+
129
+ def app(input_type: BaseType, output_type: BaseType) -> Application:
130
+ """
131
+ Helper function to create an Application.
132
+
133
+ Args:
134
+ input_type: The input type
135
+ output_type: The output type
136
+
137
+ Returns:
138
+ Application: A new Application instance
139
+ """
140
+ return Application(input_type=input_type, output_type=output_type)
@@ -0,0 +1,5 @@
1
+ from .elements import Node, Edge, node, edge
2
+ from .graph import Graph
3
+ from .connection import Connection
4
+
5
+ __all__ = ["Node", "Edge", "node", "edge", "Graph", "Connection"]