http://pyret.org/index.html
[pyret-bann]
* Why Pyret?
* Try Pyret
* News & Discussion
* Documentation
* Code
* Crew
[pyret-bann]
Pyret is a programming language designed to serve as an outstanding
choice for programming education while exploring the confluence of
scripting and functional programming. It's under active design and
development, and free to use or modify.
Check out the release notes for updates in summer 2021!
Examples Why Pyret? Set Sail News & Discussion
data BinTree:
| leaf
| node(value, left, right)
end
fun tree-sum(t):
doc: "Calculate the sum of node values"
cases (BinTree) t:
| leaf => 0
| node(v, l, r) =>
v + tree-sum(l) + tree-sum(r)
end
where:
tree-sum(leaf) is 0
node4 = node(4, leaf, leaf)
tree-sum(node(5, node4, leaf)) is 9
end
# Identifiers are immutable.
# Variables are different from identifiers.
fun mk-counter():
var ctr = 0
lam():
ctr := ctr + 1
ctr
end
where:
l1 = mk-counter()
l1() is 1
l1() is 2
l2 = mk-counter()
l2() is 1
l1() is 3
l2() is 2
end
for map(n from [list: 1,2,3]): n * n end
# ==> [list: 1, 4, 9]
for filter(n from [list: 1, 2, 3]):
n >= 2
end
# ==> [list: 2, 3]
for fold(sum from 0, n from [list: 4, 5, 6]):
sum + n
end
# ==> 15
# Note the refinements on the
# l argument and the result.
# The refinements are critical to the
# correct behavior of insertion.
fun insert(n :: Number,
l :: List % (is-sorted-ascending))
-> List % (is-sorted-ascending):
cases (List) l:
| empty => [list: n]
| link(f, r) =>
if n < f: link(n, l)
else: link(f, insert(n, r))
end
end
end
eps = 0.001
fun d-dx(f):
doc: "Calculate the derivative of f"
lam(x): (f(x + eps) - f(x)) / eps end
where:
fun square(x): x * x end
fun around(delta, target): ... end
dsquare = d-dx(square)
dsquare(5) satisfies around(0.1, 10)
dsquare(10) satisfies around(0.1, 20)
end
Programming in Pyret
fun to-celsius(f):
(f - 32) * (5 / 9)
end
for each(str from [list: "Ahoy", "world!"]):
print(str)
end
Pyret has Python-inspired syntax for functions, lists, and operators.
Iteration constructs are designed to be evocative of those in other
languages.
---------------------------------------------------------------------
Pyret makes testing a natural part of the programming process.
Functions can end in a where: clause that holds unit tests for the
function. These assertions are checked dynamically.
fun sum(l):
cases (List) l:
| empty => 0
| link(first, rest) => first + sum(rest)
end
where:
sum([list: ]) is 0
sum([list: 1, 2, 3]) is 6
end
---------------------------------------------------------------------
data BinTree:
| leaf
| node(value, left :: BinTree, right :: BinTree)
end
Pyret allows for concise, expressive, recursive data declarations.
Type annotations are optional and can be added incrementally, to
serve a variety of pedagogic styles and curricular needs.
---------------------------------------------------------------------
In addition to where: blocks, which are attached to individual
definitions (and hence usually contain unit tests), you can also
write check: blocks at the top level, for general program testing.
Both are scope delimiters. Therefore, you can use them to write local
definitions that are useful for testing but not relevant to the
program at large.
check:
fun get-status(url):
request({
url: url,
verb: "get",
params: [list: ]
}).status-code
end
get-status("http://google.com/") is 200
end
---------------------------------------------------------------------
Some thoughts on syntax
We believe indentation is critical for readable code, but we don't
want the whitespace of the program to determine its meaning. Rather,
the meaning of the program should determine its indentation
structure. Indentation becomes just another context-sensitive rule.
Unambiguous syntax (the reason for explicit end delimiters) means you
can copy-and-paste code from email or the Web, and its meaning won't
change. Your IDE can help you reindent code without worrying that
doing so will change the meaning of the program.
We haven't yet decided on the indentation rules because we want to
see the language in use for a while before we codify these.
---------------------------------------------------------------------
Real tests need to accomodate more than equality tests. Pyret
supports these generally with the satisfies form, which can be used
to check satisfaction of an arbitrary predicate.
eps = 0.001
fun d-dx(f):
doc: "Approximate the derivative of f"
lam(x): (f(x + eps) - f(x)) / eps end
where:
fun square(x): x * x end
fun around(delta, target):
lam(actual): num-abs(actual - target) < delta end
end
dsquare = d-dx(square)
dsquare(5) satisfies around(0.1, 10)
dsquare(10) satisfies around(0.1, 20)
end
---------------------------------------------------------------------
point-methods = {
method dist(self, other):
ysquared = num-expt(other.y - self.y, 2)
xsquared = num-expt(other.x - self.x, 2)
num-sqrt(ysquared + xsquared)
end
}
fun make-point(x, y):
point-methods.{ x: x, y: y }
end
check:
p1 = make-point(1, 2)
p2 = make-point(1, 5)
p1.dist(p2) is 3
end
Pyret has a straightforward object model, from which more complex
patterns can be defined. An object is defined by methods and fields
within curly braces (as in point-methods), and can be extended with .
{}. This example shows a simple class-like pattern built up from
simple objects. Objects, like most other values in Pyret, are
immutable by default, so instances of points are created by extending
an object containing point methods.
---------------------------------------------------------------------
Like what you see? Sign up for the announcements mailing list and get
notified when Pyret has a stable release. Or, if you want to try
things out in their early state, just get started!
---------------------------------------------------------------------
Highlights vs. Existing Languages
Annotations
Most "scripting" languages don't support checking annotations on
parameters out of the box, Pyret does.
Python
def square(n : int) -> int:
return n * n
square("5")
# Error at multiplication:
# Can't multiply sequence by
# non-int of type 'str'
Pyret
fun square(n :: Number) -> Number:
n * n
end
square("5")
# With type checker off:
# The Number annotation was not
# satisfied by the value "5"
# With type checker on:
# Number is incompatible with String
---------------------------------------------------------------------
Optional Annotations
But Pyret doesn't force you to annotate everything, as some other
languages do.
Java
static int square(int n) {
return n * n;
}
Pyret
fun square(n) -> Number:
n * n
end
---------------------------------------------------------------------
Refinements in Annotations
Pyret allows you to (optionally) describe refinements of data.
Python
def insert(e, s):
# tree insertion but with
# invariants neither
# stated nor checked
Pyret
fun insert(e :: Number,
s :: BST % (is-balanced))
-> BST % (is-balanced):
# self-balancing tree insertion
end
---------------------------------------------------------------------
Numbers
Pyret has numbers, because we believe an 8GB machine should not limit
students to using just 32 bits.
Java
// this is not true
((1 / 3) * 3) == 1
Pyret
# this is true
((1 / 3) * 3) == 1
---------------------------------------------------------------------
Simple Testing
Friction in the testing process makes it hard to work even simple
unit tests into early programming. Pyret removes boilerplate to put
testing in its rightful place in the programming process.
Python
import unittest
class TestLists(unittest.TestCase):
def test_empty_first(self):
self.assertRaises(IndexError, lambda: [][0])
def test_1to5(self):
self.assertEqual([1,2,3,4,5][0], 1)
def test_evens(self):
self.assertEqual([2,4,6,8][0], 2)
if __name__ == '__main__':
unittest.main()
Pyret
check:
empty.first raises "not-found"
[list: 1,2,3,4,5].first is 1
[list: 2,4,6,8].first is 2
end
---------------------------------------------------------------------
Structured Data
Being able to describe data well is central to designing and
structuring programs. Pyret offers elegant mechanisms for writing
data definitions without the cognitive or syntactic overhead of
classes. We believe the only reason __init__ will not become this
generation's public static void is that Python textbooks have begun
to shun structured data, returning us to the 1970s when everything
was squeezed into a single-dimensional data structure.
Python
class BinTree:
pass
class leaf(BinTree):
def __init__(self):
pass
class node(BinTree):
def __init__(self, v, l, r):
self.v = v
self.l = l
self.r = r
Pyret
data BinTree:
| leaf
| node(v, l, r)
end
---------------------------------------------------------------------
Structural Data
Pyret is flexible in the use of structured data, and exposes a simple
object pattern underlying it to allow for structural code alongside
more nominal patterns.
OCaml
type animal =
| Elephant of string * float
| Tiger of string * float
| Horse of string * int
...
let name_of_animal a =
match a with
| Elephant(name, _)
| Tiger(name, _)
| Horse(name, _) -> name
...
Pyret
data Animal:
| elephant(name, weight)
| tiger(name, stripes)
| horse(name, races-won)
...
end
fun animal-name(a :: Animal):
a.name
end
Racket
(struct elephant (name weight))
(struct tiger (name stripes))
(struct horse (name races-won))
...
(define (animal-name a)
(cond
[(elephant? a) (elephant-name a)]
[(tiger? a) (tiger-name a)]
[(horse? a) (horse-name a)]
...))
Pyret
data Animal:
| elephant(name, weight)
| tiger(name, stripes)
| horse(name, races-won)
...
end
fun animal-name(a :: Animal):
a.name
end
---------------------------------------------------------------------
Embracing Substitutability
A design goal of Pyret's syntax and semantics is to embrace the
substitutability of equivalent expressions as much as possible. This
is in contrast to, for example, some scripting languages, in which
what looks like binding an expression to a temporary name changes
program behavior.
JavaScript
var o = {
my_method: function(x) {
return this.y + x;
},
y: 10
}
o.my_method(5) === 15 // true
method_as_fun = o.my_method
method_as_fun(5)
// either error or NaN
// (depending on strict mode)
Pyret
o = {
method my-method(self, x): self.y + x end,
y: 10
}
method-as-fun = o.my-method
check:
o.my-method(5) is 15
method-as-fun(5) is 15
end
Ruby
o = Object.new
def o.my_method(x)
self.y + x
end
def o.y
10
end
o.my_method(5) == 15 # true
method_as_fun = o.my_method
# Wrong number of arguments, 0 for 1
Pyret
o = {
method my-method(self, x): self.y + x end,
y: 10
}
method-as-fun = o.my-method
check:
o.my-method(5) is 15
method-as-fun(5) is 15
end
---------------------------------------------------------------------
Site generated by Frog, the frozen blog tool.