pyfun-lang 0.0.1__py3-none-win_amd64.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.
- pyfun_lang-0.0.1.data/scripts/pyfun.exe +0 -0
- pyfun_lang-0.0.1.dist-info/METADATA +426 -0
- pyfun_lang-0.0.1.dist-info/RECORD +7 -0
- pyfun_lang-0.0.1.dist-info/WHEEL +4 -0
- pyfun_lang-0.0.1.dist-info/licenses/LICENSE +201 -0
- pyfun_lang-0.0.1.dist-info/licenses/NOTICE +15 -0
- pyfun_lang-0.0.1.dist-info/sboms/pyfun.cyclonedx.json +70 -0
|
Binary file
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyfun-lang
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: Intended Audience :: Education
|
|
7
|
+
Classifier: Programming Language :: Python
|
|
8
|
+
Classifier: Programming Language :: Rust
|
|
9
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Summary: An F#-inspired, functional-first language that compiles to readable Python.
|
|
13
|
+
Keywords: functional,compiler,fsharp,transpiler,education
|
|
14
|
+
Author: Simon Treanor
|
|
15
|
+
License-Expression: Apache-2.0
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
18
|
+
Project-URL: Repository, https://github.com/simontreanor/Pyfun
|
|
19
|
+
|
|
20
|
+
# Pyfun
|
|
21
|
+
|
|
22
|
+
**Functional programming for the language classrooms already teach.**
|
|
23
|
+
|
|
24
|
+
Pyfun is an F#-inspired, functional-first language that compiles to readable Python. It brings
|
|
25
|
+
algebraic data types, exhaustive matching, currying, inferred effects, and units of measure to the
|
|
26
|
+
Python ecosystem, and its Rust compiler checks every one of them before a single line of Python is
|
|
27
|
+
emitted.
|
|
28
|
+
|
|
29
|
+
It exists to make functional programming teachable where students already are. CS courses run on
|
|
30
|
+
Python; learning FP usually means leaving it for Haskell, OCaml, or F# and adopting a whole new
|
|
31
|
+
ecosystem students rarely touch again. Pyfun keeps them in Python, with no new runtime and no new
|
|
32
|
+
package manager, and compiles to Python they can read, so every concept stays visible in the code
|
|
33
|
+
they already understand.
|
|
34
|
+
|
|
35
|
+
```fsharp
|
|
36
|
+
type Shape = Circle float | Rect float float
|
|
37
|
+
|
|
38
|
+
# `area` handles Circle but forgets Rect, so Pyfun refuses to compile it:
|
|
39
|
+
let area s =
|
|
40
|
+
match s:
|
|
41
|
+
case Circle r: 3.14159 * r * r
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```console
|
|
45
|
+
$ pyfun check shapes.pyfun
|
|
46
|
+
error: non-exhaustive match: `Rect _ _` is not matched
|
|
47
|
+
--> 4:3
|
|
48
|
+
|
|
|
49
|
+
4 | match s:
|
|
50
|
+
| ^^^^^^^^
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> Plain Python compiles and runs this, then silently returns `None` the day a `Rect` reaches it,
|
|
54
|
+
> and you debug the `TypeError` an hour downstream. Pyfun's Rust compiler checks types, effects,
|
|
55
|
+
> units, and match exhaustiveness **before a single line of Python is emitted**, then hands you
|
|
56
|
+
> code you can read, diff, and ship.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Made for the classroom
|
|
61
|
+
|
|
62
|
+
Teaching FP normally forces a detour: a new language, a new toolchain, and a new ecosystem the
|
|
63
|
+
students abandon the moment the course ends. Pyfun removes the detour.
|
|
64
|
+
|
|
65
|
+
- **They already have the runtime.** Pyfun compiles to plain Python, so anything a student writes
|
|
66
|
+
runs on the interpreter already installed on every lab machine. No VM, no new package manager.
|
|
67
|
+
- **The concepts stay visible.** `pyfun compile` shows the Python your functional code becomes, so a
|
|
68
|
+
student watches an ADT turn into a class, a `match` into `match`/`case`, and currying into a
|
|
69
|
+
closure. They learn the idea and how it maps to the imperative code they know.
|
|
70
|
+
- **Good habits are enforced, not suggested.** The compiler refuses to skip a case, ignore a `None`,
|
|
71
|
+
or mutate what should stay immutable, so students learn to handle every path because the tool insists.
|
|
72
|
+
- **A small, learnable core.** Pyfun is deliberately compact, so the language stays out of the way of
|
|
73
|
+
the ideas you are teaching.
|
|
74
|
+
|
|
75
|
+
Pyfun is a real, general-purpose language, not a toy. But teaching is why it exists.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Why Pyfun over plain Python?
|
|
80
|
+
|
|
81
|
+
Python is the best ecosystem in the world, and even with `mypy`/`pyright` bolted on, large Python
|
|
82
|
+
programs still fail in ways a compiler could have caught. Pyfun keeps the ecosystem and makes the
|
|
83
|
+
checks mandatory.
|
|
84
|
+
|
|
85
|
+
| | Plain Python | Pyfun |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| **Type errors** | `mypy`/`pyright` are optional and unsound; they warn, they don't gate | found at compile time; no Python is emitted until they pass |
|
|
88
|
+
| **`None` handling** | `AttributeError: 'NoneType'…` | `Option a` with exhaustive `match`; the compiler makes you handle `None` |
|
|
89
|
+
| **Missing `case`** | silently falls through, returns `None` | **exhaustiveness error** with a concrete missing-case witness |
|
|
90
|
+
| **Mutation** | everything is mutable, everywhere | immutable by default; `let mut` + `<-` is opt-in and tracked |
|
|
91
|
+
| **Side effects** | invisible | **inferred and tracked**; `let pure` is a compile-checked promise |
|
|
92
|
+
| **Units / dimensions** | a comment and a prayer | `10<N> / 2<m^2> : float<Pa>`, checked and then erased |
|
|
93
|
+
| **Runtime** | CPython | **CPython**: Pyfun *is* Python once compiled |
|
|
94
|
+
|
|
95
|
+
**Why not just `mypy`/`pyright`?** They're a gradual, optional overlay: unsound by design, never
|
|
96
|
+
required, and one `# type: ignore` from silence. They report; they don't gate. Pyfun makes the same
|
|
97
|
+
class of check *mandatory*: it blocks compilation, infers the signatures pyright often needs spelled
|
|
98
|
+
out, and there is no untyped Pyfun to fall back to. And you keep the entire Python ecosystem while
|
|
99
|
+
you do it.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Type-checked Python interop
|
|
104
|
+
|
|
105
|
+
`extern` imports **any** Python callable or value at a Pyfun type. The dotted target is imported
|
|
106
|
+
for you; the boundary is *effectful by default* (a Python call can do anything), and `pure` opts
|
|
107
|
+
out where you know better. Once imported, the function is a first-class curried Pyfun value:
|
|
108
|
+
type-checked, effect-tracked, and partially applicable.
|
|
109
|
+
|
|
110
|
+
```fsharp
|
|
111
|
+
extern pure mean: List float -> float = statistics.mean
|
|
112
|
+
extern pure stdev: List float -> float = statistics.stdev
|
|
113
|
+
|
|
114
|
+
type Summary = { n: int, mean: float, stdev: float }
|
|
115
|
+
|
|
116
|
+
let summarize xs =
|
|
117
|
+
Summary { n = List.len xs, mean = mean xs, stdev = stdev xs }
|
|
118
|
+
|
|
119
|
+
let report xs =
|
|
120
|
+
let s = summarize xs
|
|
121
|
+
f"n={s.n} mean={s.mean} sd={s.stdev}"
|
|
122
|
+
|
|
123
|
+
print (report [1.0, 2.0, 3.0, 4.0])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`pyfun compile` turns that into Python you'd be happy to have written by hand:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
import statistics
|
|
130
|
+
|
|
131
|
+
class Summary:
|
|
132
|
+
__match_args__ = ('n', 'mean', 'stdev')
|
|
133
|
+
def __init__(self, n, mean, stdev):
|
|
134
|
+
self.n = n
|
|
135
|
+
self.mean = mean
|
|
136
|
+
self.stdev = stdev
|
|
137
|
+
def __repr__(self):
|
|
138
|
+
return f"Summary({self.n!r}, {self.mean!r}, {self.stdev!r})"
|
|
139
|
+
# ...structural __eq__/__hash__/ordering elided...
|
|
140
|
+
|
|
141
|
+
def summarize(xs):
|
|
142
|
+
return Summary(len(xs), statistics.mean(xs), statistics.stdev(xs))
|
|
143
|
+
|
|
144
|
+
def report(xs):
|
|
145
|
+
s = summarize(xs)
|
|
146
|
+
return f"n={s.n} mean={s.mean} sd={s.stdev}"
|
|
147
|
+
|
|
148
|
+
print(report([1.0, 2.0, 3.0, 4.0]))
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```console
|
|
152
|
+
$ pyfun run stats.pyfun
|
|
153
|
+
n=4 mean=2.5 sd=1.2909944487358056
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Notice what the compiler does:
|
|
157
|
+
|
|
158
|
+
- **No wrapper layer.** `statistics.mean(xs)` is called directly. `List` *is* a Python `list`,
|
|
159
|
+
and a Pyfun record *is* a plain class. There is no runtime, no VM, no marshalling.
|
|
160
|
+
- **Effects tracked across the boundary.** A bare `extern` is `io` at full application, so it
|
|
161
|
+
can't be called from a `let pure`. Mark it `pure` (like `statistics.mean`) and it composes into
|
|
162
|
+
pure code. You can even annotate other effect labels: `extern fetch: string ->{async} string = httpx.get`.
|
|
163
|
+
- **Exceptions become values.** `try (parseInt s) : Result int Exception` catches whatever the
|
|
164
|
+
Python side raises and hands you a `Result` to `match` on. The imperative FFI edge becomes the
|
|
165
|
+
FP error type, with `errorKind` and `errorMessage` fields.
|
|
166
|
+
|
|
167
|
+
```fsharp
|
|
168
|
+
extern parseInt: string -> int = int # Python's built-in int()
|
|
169
|
+
|
|
170
|
+
let safe s = Result.withDefault 0 (try (parseInt s))
|
|
171
|
+
print (safe "42") # 42
|
|
172
|
+
print (safe "oops") # 0 (the ValueError was caught into an Error)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## A whistle-stop tour
|
|
178
|
+
|
|
179
|
+
Everything below type-checks, compiles, and runs today. See
|
|
180
|
+
[`examples/hello.pyfun`](examples/hello.pyfun) for the exhaustive version.
|
|
181
|
+
|
|
182
|
+
**Algebraic data types, records, and exhaustive matching.** `None` cannot bite you:
|
|
183
|
+
|
|
184
|
+
```fsharp
|
|
185
|
+
type Shape = Circle float | Rect float float
|
|
186
|
+
|
|
187
|
+
let area s =
|
|
188
|
+
match s:
|
|
189
|
+
case Circle r: 3.14159 * r * r
|
|
190
|
+
case Rect w h: w * h
|
|
191
|
+
# forget a case and the compiler reports the missing witness, e.g. `Rect _ _ is not matched`
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Pipelines, currying, composition.** F#'s `|>`, `<|`, `>>`, `<<`, and operator sections `(+)`:
|
|
195
|
+
|
|
196
|
+
```fsharp
|
|
197
|
+
let describe =
|
|
198
|
+
List.filter (fun x -> x > 0)
|
|
199
|
+
>> List.map ((*) 2)
|
|
200
|
+
>> List.fold (+) 0
|
|
201
|
+
|
|
202
|
+
let total = [1, -2, 3] |> describe # (1 + 3) * 2 = 8
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Inferred effects.** Purity is a checked promise, never boilerplate:
|
|
206
|
+
|
|
207
|
+
```fsharp
|
|
208
|
+
let pure add a b = a + b # OK: no effects
|
|
209
|
+
# let pure shout n = print n # compile error: `print` performs `io`
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Units of measure.** Dimensional analysis at compile time, erased at runtime:
|
|
213
|
+
|
|
214
|
+
```fsharp
|
|
215
|
+
measure m
|
|
216
|
+
measure s
|
|
217
|
+
measure kg
|
|
218
|
+
measure N = kg m / s^2 # derived aliases expand to base units
|
|
219
|
+
|
|
220
|
+
let speed = 100<m> / 10<s> # float<m/s>
|
|
221
|
+
let force = 10<N>
|
|
222
|
+
# let bad = 100<m> + 10<s> # compile error: m vs s
|
|
223
|
+
let side = sqrt 16.0<m^2> # float<m>, unit-aware roots
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Computation expressions** (F#'s showcase feature): `result`, `seq`, `async`, plus your own:
|
|
227
|
+
|
|
228
|
+
```fsharp
|
|
229
|
+
let checked ok v =
|
|
230
|
+
result { # railway-oriented; short-circuits on Error
|
|
231
|
+
let! x = if ok then Ok v else Error "bad"
|
|
232
|
+
return x + 1
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Rich literals and strings.** F-strings, raw strings, triple-quotes, scientific notation,
|
|
237
|
+
digit separators, hex/octal/binary:
|
|
238
|
+
|
|
239
|
+
```fsharp
|
|
240
|
+
let planck = 6.626e-34
|
|
241
|
+
let million = 1_000_000
|
|
242
|
+
let mask = 0xFF
|
|
243
|
+
let who = "Ada"
|
|
244
|
+
let line = f"{who} scored {million} ({String.upper who})"
|
|
245
|
+
let path = r"C:\Users\pyfun" # raw string, backslashes literal
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
And a standard library that reads like F#'s: module-qualified `List` / `Set` / `Map` / `Option` /
|
|
249
|
+
`Result` / `Seq` / `String` (`List.map`, `Map.tryFind`, `Result.bind`, lazy `Seq.take`,
|
|
250
|
+
`String.split`), tuples, active patterns, typed holes for type-driven development, and
|
|
251
|
+
multi-file projects with `import`.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## How Pyfun compares
|
|
256
|
+
|
|
257
|
+
A few projects bring functional or statically-typed code to Python. Here is the field, and the
|
|
258
|
+
bet Pyfun makes within it:
|
|
259
|
+
|
|
260
|
+
- **[Fable](https://fable.io)** compiles real F# to Python, the most capable option by far, because
|
|
261
|
+
it *is* F#, with the whole language and a mature ecosystem. The trade-offs: it needs the **.NET
|
|
262
|
+
toolchain**, and its output depends on a **runtime library** (`fable_library`).
|
|
263
|
+
- **[Erg](https://erg-lang.org)** is a statically-typed, Python-compatible language with a rich type
|
|
264
|
+
system and marker-based effect control. It is the closest to Pyfun in ambition, though "rusty"/OO
|
|
265
|
+
rather than ML-family, with *explicit* effect annotations.
|
|
266
|
+
- **[Coconut](https://coconut-lang.org)** is a functional *superset* of Python; static typing is an
|
|
267
|
+
optional MyPy add-on, so nothing is enforced.
|
|
268
|
+
- **Dynamic dialects** (**Hy**, **Mochi**, **Dogelang**) are dynamically-typed FP/Lisp languages
|
|
269
|
+
that run on Python; they share the last column, since they trade static guarantees for Python's
|
|
270
|
+
dynamism.
|
|
271
|
+
|
|
272
|
+
Legend: ✅ yes · ⚠️ partial · ➖ different approach · ❌ no
|
|
273
|
+
|
|
274
|
+
| | **Pyfun** | **Fable** | **Erg** | **Coconut** | **Dynamic dialects** |
|
|
275
|
+
|---|:--:|:--:|:--:|:--:|:--:|
|
|
276
|
+
| FP-first language (not a Python superset) | ✅ | ✅ | ✅ | ➖ | ⚠️ |
|
|
277
|
+
| ML / F#-family syntax | ✅ | ✅ | ➖ | ➖ | ❌ |
|
|
278
|
+
| **Mandatory** static typing | ✅ | ✅ | ✅ | ❌ | ❌ |
|
|
279
|
+
| Type inference | ✅ | ✅ | ✅ | ➖ | ❌ |
|
|
280
|
+
| **Zero** annotations required | ✅ | ⚠️ | ⚠️ | ❌ | ➖ |
|
|
281
|
+
| ADTs + **enforced** exhaustiveness | ✅ | ✅ | ⚠️ | ⚠️ | ❌ |
|
|
282
|
+
| **Inferred** effects (never annotated) | ✅ | ❌ | ➖ | ❌ | ❌ |
|
|
283
|
+
| Units of measure | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
284
|
+
| Computation expressions | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
285
|
+
| Nested record-update (`{ p with a.b = v }`) | ✅ | ✅ | ❌ | ❌ | ➖ |
|
|
286
|
+
| Typed holes (type-driven dev) | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
287
|
+
| Chained comparisons (`a < b < c`) | ✅ | ❌ | ⚠️ | ✅ | ✅ |
|
|
288
|
+
| Compiler-as-gatekeeper | ✅ | ✅ | ✅ | ❌ | ❌ |
|
|
289
|
+
| **Self-contained output** (no runtime library) | ✅ | ❌ | ❌ | ❌ | ➖ |
|
|
290
|
+
| **No .NET / host-runtime toolchain** | ✅ | ❌ | ✅ | ➖ | ➖ |
|
|
291
|
+
| Python-library interop | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
292
|
+
| Maturity / production use | ❌ pre-1.0 | ⚠️ Py beta | ⚠️ | ✅ | ⚠️ |
|
|
293
|
+
| Language surface (built-in constructs) | ⚠️ small core | ✅ full F# | ⚠️ | ✅ Python superset | ✅ |
|
|
294
|
+
| Community, docs, support | ❌ solo | ✅ | ⚠️ | ✅ | ⚠️ |
|
|
295
|
+
|
|
296
|
+
Pyfun's strengths are the bold rows: **self-contained, runtime-free Python output** (a `List` is a
|
|
297
|
+
`list`, a record is a plain class), a **single dependency-free compiler** with no .NET, **inferred
|
|
298
|
+
effects**, and a language **designed for Python interop first**. On several rows it reaches past F#
|
|
299
|
+
itself, borrowing inferred effects from Koka, typed holes from Haskell and Idris, and Python-style
|
|
300
|
+
chained comparisons. Every tool here reaches the full Python ecosystem
|
|
301
|
+
(the interop row), so Pyfun's small core costs nothing in libraries; it just buys simplicity.
|
|
302
|
+
|
|
303
|
+
Reach for Fable when you want all of F# and are happy to bring the .NET toolchain and a runtime
|
|
304
|
+
library along. Reach for Pyfun when you want the emitted Python to be a first-class, readable
|
|
305
|
+
artifact you own outright, or when you are teaching functional programming to people who live in
|
|
306
|
+
Python.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Getting started
|
|
311
|
+
|
|
312
|
+
Pyfun runs on the Python you already have. You need two things:
|
|
313
|
+
|
|
314
|
+
- **Python 3.12+** on your `PATH`, to run the emitted code.
|
|
315
|
+
- **[Rust](https://rustup.rs/)** to build the compiler (it auto-selects the pinned 1.96 toolchain).
|
|
316
|
+
*Prebuilt binaries and a `pip install` path are on the roadmap, which will remove this step.*
|
|
317
|
+
|
|
318
|
+
**Install the `pyfun` compiler** onto your `PATH`:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
cargo install --git https://github.com/simontreanor/Pyfun pyfun
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Or build from a clone, if you want to hack on the compiler itself:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
git clone https://github.com/simontreanor/Pyfun
|
|
328
|
+
cd Pyfun
|
|
329
|
+
cargo install --path .
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Try it** on the bundled examples:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
pyfun run examples/hello.pyfun # compile, then execute via python
|
|
336
|
+
pyfun check examples/hello.pyfun # type-check, rustc-style diagnostics
|
|
337
|
+
pyfun repl # interactive REPL
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Write your first program.** Save this as `hello.pyfun`:
|
|
341
|
+
|
|
342
|
+
```fsharp
|
|
343
|
+
type Shape = Circle float | Rect float float
|
|
344
|
+
|
|
345
|
+
let area s =
|
|
346
|
+
match s:
|
|
347
|
+
case Circle r: 3.14159 * r * r
|
|
348
|
+
case Rect w h: w * h
|
|
349
|
+
|
|
350
|
+
print (area (Circle 2.0))
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Then run it (prints `12.56636`), or see the Python it becomes:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
pyfun run hello.pyfun # 12.56636
|
|
357
|
+
pyfun compile hello.pyfun # emit readable Python to stdout
|
|
358
|
+
pyfun compile hello.pyfun -o hello.py
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Multi-file projects just work: `import Geometry` pulls in a sibling `geometry.pyfun`, and any
|
|
362
|
+
command drives the whole graph (`pyfun run examples/modules/main.pyfun`).
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Editor support
|
|
367
|
+
|
|
368
|
+
Pyfun ships a dependency-free language server (`pyfun lsp`) and a VS Code client in
|
|
369
|
+
[`editors/vscode/`](editors/vscode/). Over resilient analysis that survives a half-typed file, you get:
|
|
370
|
+
|
|
371
|
+
- **Diagnostics** as you type
|
|
372
|
+
- **Hover** showing the inferred type *and* effect of any expression, binding, or parameter
|
|
373
|
+
- **Go-to-definition** and **find-references**, across files
|
|
374
|
+
- **Rename**, project-wide, for values, constructors, and types
|
|
375
|
+
- **Completion**, **document symbols**, and **workspace symbols**
|
|
376
|
+
|
|
377
|
+
Build and install the extension from [`editors/vscode/`](editors/vscode/); once `pyfun` is on your
|
|
378
|
+
`PATH`, it launches `pyfun lsp` automatically.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## How it works
|
|
383
|
+
|
|
384
|
+
Pyfun is a dependency-free Rust crate that runs a classic pipeline, and the compiler is the
|
|
385
|
+
gatekeeper: **nothing is emitted until every check passes.**
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
.pyfun ──► lexer ──► parser ──► Hindley–Milner type inference ──► Python-AST IR ──► readable .py
|
|
389
|
+
│ │ (+ effects, units, exhaustiveness) │
|
|
390
|
+
offside recursive lowered, not
|
|
391
|
+
rule descent string-spliced
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
- **Type inference** is full HM with let-generalization: you never annotate a value. The only
|
|
395
|
+
types you write are in `type`/`extern` declarations, and every signature is inferred. It also does
|
|
396
|
+
unit-of-measure inference (abelian-group unification), effect-row inference, and Maranget-style
|
|
397
|
+
exhaustiveness with concrete witnesses.
|
|
398
|
+
- **Lowering** targets a Python-AST IR and emits real, formatted Python: curried functions
|
|
399
|
+
collapse to n-ary `def`s and direct calls (closures only for genuine partial application), CEs
|
|
400
|
+
desugar to their natural Python (`async`/`await`, generators, railway `Result`), and units erase.
|
|
401
|
+
- **No CPython fork.** Pyfun is a front end for the Python ecosystem, not a competing runtime.
|
|
402
|
+
|
|
403
|
+
The full language design and rationale live in [`DESIGN.md`](DESIGN.md).
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Status
|
|
408
|
+
|
|
409
|
+
MVP showcase complete and runnable: ADTs, records, tuples, computation expressions (including
|
|
410
|
+
user-defined builders), units of measure, mutability, inferred multi-label effects, general Python
|
|
411
|
+
FFI via `extern`, a module-qualified standard library, string interpolation, active patterns,
|
|
412
|
+
typed holes, file-based modules, and a full LSP. See [`ROADMAP.md`](ROADMAP.md) for what's next.
|
|
413
|
+
|
|
414
|
+
This is a solo, actively-developed project: the MVP is feature-complete and runnable, but it's
|
|
415
|
+
pre-1.0. Expect sharp edges; the language surface is stabilizing but not frozen.
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## License
|
|
420
|
+
|
|
421
|
+
Pyfun is free and open source under the **[Apache License 2.0](LICENSE)**: use, modify, and
|
|
422
|
+
redistribute it, including commercially. The accompanying [`NOTICE`](NOTICE) names **Simon Treanor**
|
|
423
|
+
as the original author; keep it with any redistribution or derivative work.
|
|
424
|
+
|
|
425
|
+
Copyright © 2026 Simon Treanor.
|
|
426
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
pyfun_lang-0.0.1.data/scripts/pyfun.exe,sha256=ONYG0soqF4JUfCAuxsFCugLKbDB81rKDoOGNqmr_COU,2545664
|
|
2
|
+
pyfun_lang-0.0.1.dist-info/METADATA,sha256=arzv0P9OzwvsPb5v3RPPN32VWeW31FuDx_NKAq0thw8,17801
|
|
3
|
+
pyfun_lang-0.0.1.dist-info/WHEEL,sha256=2zDlIYIdD4m4N3p5DVEG3iJhGLdhsBQgdH-FqVkAur8,94
|
|
4
|
+
pyfun_lang-0.0.1.dist-info/licenses/LICENSE,sha256=WfhY6jTFgvhspmH4_bSjiDobcsOuwCOqEnlrHq7-2es,11544
|
|
5
|
+
pyfun_lang-0.0.1.dist-info/licenses/NOTICE,sha256=0dh8STks8l8o3qwuJkERNTfbhR_dS1FEcGpyKDJm1C0,665
|
|
6
|
+
pyfun_lang-0.0.1.dist-info/sboms/pyfun.cyclonedx.json,sha256=MvVzbYvaXOUa0boFOYHlWn4hM9WaEz79nbAfB2iF-eg,1815
|
|
7
|
+
pyfun_lang-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Simon Treanor
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Pyfun
|
|
2
|
+
Copyright (c) 2026 Simon Treanor
|
|
3
|
+
|
|
4
|
+
This product includes software originally designed and written by
|
|
5
|
+
Simon Treanor (the "Original Author").
|
|
6
|
+
|
|
7
|
+
Pyfun is an FP-first programming language that compiles to readable Python,
|
|
8
|
+
together with its Rust compiler, type checker, language server, and tooling.
|
|
9
|
+
The language design, compiler architecture, and this codebase are the
|
|
10
|
+
original work of Simon Treanor.
|
|
11
|
+
|
|
12
|
+
If you redistribute this software or any derivative work, you must retain
|
|
13
|
+
this NOTICE file and the accompanying LICENSE, and you must preserve the
|
|
14
|
+
attribution to the Original Author, in accordance with the terms of the
|
|
15
|
+
Apache License, Version 2.0.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"bomFormat": "CycloneDX",
|
|
3
|
+
"specVersion": "1.5",
|
|
4
|
+
"version": 1,
|
|
5
|
+
"serialNumber": "urn:uuid:eabf6451-2935-49dd-981f-064e436fc2a5",
|
|
6
|
+
"metadata": {
|
|
7
|
+
"timestamp": "2026-07-05T15:15:46.747191100Z",
|
|
8
|
+
"tools": [
|
|
9
|
+
{
|
|
10
|
+
"vendor": "CycloneDX",
|
|
11
|
+
"name": "cargo-cyclonedx",
|
|
12
|
+
"version": "0.5.9"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"authors": [
|
|
16
|
+
{
|
|
17
|
+
"name": "Simon Treanor"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"component": {
|
|
21
|
+
"type": "application",
|
|
22
|
+
"bom-ref": "path+file:///D:/a/Pyfun/Pyfun#pyfun@0.0.1",
|
|
23
|
+
"author": "Simon Treanor",
|
|
24
|
+
"name": "pyfun",
|
|
25
|
+
"version": "0.0.1",
|
|
26
|
+
"description": "An F#-inspired, functional-first language that compiles to readable Python.",
|
|
27
|
+
"scope": "required",
|
|
28
|
+
"licenses": [
|
|
29
|
+
{
|
|
30
|
+
"expression": "Apache-2.0"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"purl": "pkg:cargo/pyfun@0.0.1?download_url=file://.",
|
|
34
|
+
"externalReferences": [
|
|
35
|
+
{
|
|
36
|
+
"type": "vcs",
|
|
37
|
+
"url": "https://github.com/simontreanor/Pyfun"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"components": [
|
|
41
|
+
{
|
|
42
|
+
"type": "library",
|
|
43
|
+
"bom-ref": "path+file:///D:/a/Pyfun/Pyfun#pyfun@0.0.1 bin-target-0",
|
|
44
|
+
"name": "pyfun",
|
|
45
|
+
"version": "0.0.1",
|
|
46
|
+
"purl": "pkg:cargo/pyfun@0.0.1?download_url=file://.#src/lib.rs"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "application",
|
|
50
|
+
"bom-ref": "path+file:///D:/a/Pyfun/Pyfun#pyfun@0.0.1 bin-target-1",
|
|
51
|
+
"name": "pyfun",
|
|
52
|
+
"version": "0.0.1",
|
|
53
|
+
"purl": "pkg:cargo/pyfun@0.0.1?download_url=file://.#src/main.rs"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"properties": [
|
|
58
|
+
{
|
|
59
|
+
"name": "cdx:rustc:sbom:target:all_targets",
|
|
60
|
+
"value": "true"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"components": [],
|
|
65
|
+
"dependencies": [
|
|
66
|
+
{
|
|
67
|
+
"ref": "path+file:///D:/a/Pyfun/Pyfun#pyfun@0.0.1"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|