GithubHelp home page GithubHelp logo

soonad / formality-core Goto Github PK

View Code? Open in Web Editor NEW
39.0 8.0 11.0 618 KB

Specification of the Formality proof and programming language

License: MIT License

JavaScript 78.65% Python 5.85% Haskell 15.49%
formality funcional-programming lambda-calculus programming-language type-system

formality-core's Introduction

Formality-Core

A lighweight proof language. Whitepaper.

Installation

Formality-Core has multiple reference implementations. Currently, the easiest to install uses JavaScript. First, install npm in your system. Then, on the command line, type: npm -g formality-core. If all goes well, the language should be accessible via the fm command.

Using

To use it, save a .fm file. For example, save the file below as main.fm:

main : <A: Type> -> A -> A
  <A> (x) x

And type fm main. This should output:

Type-checking main.fm:
main : <A: Type> -> A -> A

All terms check.

Evaluating `main`:
(x) x

You can also compile .fm files to JavaScript. First, run fm to generate a .fmc. Then, run fmcjs main. You can also compile to Haskell with fmchs main. You can run a script with fmcio main. In this case, main must have an IO type.

Contributing

Since Formality-Core is so simple, it doesn't come with built-in functions you would expect, and it doesn't have a standard library. But you're welcome to clone the Moonad repository, where we're building several common data structures and algorithms, and contribute!

formality-core's People

Contributors

gabriel-barrett avatar johnchandlerburnham avatar maisamilena avatar victortaelin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

formality-core's Issues

Can we derive function extensionality from self types only?

The main insight behind self types is easy to explain. Simple functions can be written as A -> B, and an application f a has type B. Dependent functions can be written as (x: A) -> B x, and an application f a has type B a. The insight is that the type returned by an application has access to the value of the argument a. Self dependent functions can be written as s(x: A) -> B s x, and an application f a has type B f a. The insight is that the type returned by an application can also access the value of the function f! This simple, elegant extension allow us to construct inductive datatypes with lambdas.

It is not rare for generalisations to allow us to do things beyond the concrete reasons they were discovered. So, a question is, what else self-types allow us to do? A very interesting thing we just found is the concept of smart-constructors, i.e., constructors that compute. With them, we can define an Int type as a pair of Nat such that the constructor itself canonicalizes its values, i.e., (4,2) becomes (2,0), (3,5) becomes (0,2), etc. We can then trivially prove that (a,b) == (succ(a), succ(b)). This is as elegant as the quotiented formalization, without the computational blowup.

What would be really great is an implementation of intervals, i.e., a type inhabited by two definitionally equal values, i0 and i1 such that i0 == i1. We've got surprisingly close by adding a phantom constructor ie : i0 == i1 to the self encoding. It works as expected: to eliminate an interval, one must provide a proof that both returned values are equal. But there is one thing missing: the construction ie itself, left as an axiom. If we managed to construct it, then Funext would follow trivially. Even if we don't, this might point to a simple primitive that could allow us to have it without needing to add i0 and i1 as native concepts.

If anyone is willing to try, I've made this self-sufficient Formality-Core file:

// To type-check, install npm:
// $ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
// $ nvm install 13.10.1
// 
// Then install Formality-Core:
// $ npm i -g [email protected]
// 
// Then download this file:
// $ curl https://gist.githubusercontent.com/MaiaVictor/28e3332c19fbd80747a0dceb33896b6b/raw/638d0ee9b623b02e5484d18c2be593fbc40183f2/intervals_with_self_types.fmc.c >> main.fmc
// 
// Then just run `fmc` in the same dir:
// $ fmc

// The syntax is simple.
// `(x) x`        is a lambda
// `f(x)`         is an application
// `f|x;`         is an alternative syntax for a multi-line application
// `s(x: A) -> B` is a dependent function type
// `<x> x`        is a computationally irrelevant lambda
// `f<x>`         is a computationally irrelevant application
// `s<x: A> -> B` is a computationally irrelevant dependent function type

// The main insight behind self-types is easy to explain. Simple functions can
// be written as `A -> B`, and an application `f a` has type `B`. Dependent
// functions can be written as `(x: A) -> B x`, and an application `f a` has
// type `B a`. The great insight behind dependent functions is that the type
// returned by an application has access to the value of the argument `a`.  Self
// dependent functions can be written as `s(x: A) -> B s x`, and an application
// `f a` has type `B f a`. The insight is that the type returned by an
// application can also access the value of the function `f`! This simple,
// elegant extension allow us to construct inductive datatypes with lambdas.

// Bool
// ====
// Bools can be defined as their dependent eliminations, using a self var `b`.

Bool: Type
  b<P: Bool -> Type> ->
  (True: P(true)) ->
  (False: P(false)) ->
  P(b)

true: Bool
  <P> (t) (f) t

false: Bool
  <P> (t) (f) f

// Nat
// ===
// Nats can be defined as their dependent eliminations, using a self var `n`.

Nat: Type
  n<P: Nat -> Type> ->
  (Zero: P(zero)) ->
  (Succ: (pred: Nat) -> P(succ(pred))) ->
  P(n)

zero: Nat
  <P> (z) (s) z

succ: Nat -> Nat
  (n) <P> (z) (s) s(n)

// Equality
// ========
// Equality can be defined as its dependent elimination, i.e., the J-axiom.

Id: (A: Type) -> A -> A -> Type
  (A) (a) (b)
  e<P: (b: A) -> Id(A)(a)(b) -> Type> ->
  (Refl: P(a)(refl<A><a>)) ->
  P(b)(e)

// A proof that a value is equal to itself.
refl: <A: Type> -> <a: A> -> Id(A)(a)(a)
  <A> <a>
  <P> (refl) refl

// If `a == b`, then `P(a)` implies `P(b)`.
rewrite: <A: Type> -> <a: A> -> <b: A> -> <P: A -> Type> -> <e: Id(A)(a)(b)> -> P(a) -> P(b)
  <A> <a> <b> <P> <e> (x)
  e<(b) (a) P(b)>(x)

// Ints
// ====
// Integers can be defined as a pair of nats quotiented by `(x,y) == (x+1,y+1)`.
// With self-types, we can do it with a smart constructor that normalizes both
// numbers to canonical form, i.e., (4,2) becomes (2,0), (3,5) becomes (0,2)...

Int: Type
  i<P: Int -> Type> ->
  (New: (x: Nat) -> (y: Nat) -> P(int(x)(y))) ->
  P(i)

int: Nat -> Nat -> Int
  (x) (y)
  <P> (new)
  x<(x) P(int(x)(y))>
  | new(zero)(y);
  | (x.pred) y<(y) P(int(succ(x.pred))(y))>
    | new(succ(x.pred))(zero);
    | (y.pred) int(x.pred)(y.pred)<P>(new);;

// Proof that `int(x)(y) == int(succ(x))(succ(y))`.
theorem: (x: Nat) -> (y: Nat) -> Id(Int)(int(x)(y))(int(succ(x))(succ(y)))
  (x) (y) refl<Int><int(x)(y)>

// Intervals
// =========
// Intervals can be defined with an extra constructor for `i0 == i1`, as in:
// data I : Set where
//   i0 : I
//   i1 : I
//   ie : i0 == i1

I: Type
  i<P: I -> Type> ->
  (I0: P(i0)) ->
  (I1: P(i1)) ->
  (Ie: Id(P(i0))(I0)(rewrite<I><i1><i0><P><ie>(I1))) ->
  P(i)

i0: I
  <P> (a) (b) (e)
  a

i1: I
  <P> (a) (b) (e)
  b

// But this is left as an axiom. Can we actually construct it?
ie: Id(I)(i1)(i0)
  ie

// Tests
// =====

// We can convert `i` to `true`.
i_to_true: (i: I) -> Bool
  (i)
  i<() Bool>
  | true;
  | true;
  | refl<Bool><true>;
  
// We can convert `i` to `false`.
i_to_false: (i: I) -> Bool
  (i)
  i<() Bool>
  | false;
  | false;
  | refl<Bool><false>;

// But we can't convert `i` to `true` or `false` depending on its value.
// distinguish_i: (i: I) -> Bool
//  (i)
//  i<() Bool>
//  | true;
//  | false;
//  | Type; // impossible: demands a proof that `true == false`

// Problem: can we define `ie`?

Formality-Core is just an implementation of a dependently typed language with self types, a fast type checker and nice error messages. Running it is extremely easy (5 commands in a Linux environment), so give it a try! If you have any reason to suspect this is either possible or not, or any pointers to how we could change the language as slightly as possible to complete this proof, we'd be thrilled to know.

Note: you need to use [email protected] since it considered the application of an erased f to f(x) equal to x. We don't do that anymore (erasures don't affect type-checking at all and may even be removed from core soon).

Possible error in JS typechecker

If we take the Bool type and switch the argument order in the self-type:

Boole : Type
  boole_value<P: Boole -> Type> ->
  P(ff) -> 
  P(tt) -> 
  P(boole_value)

ff: Boole
  <P> (t) (f) f

tt: Boole
  <P> (t) (f) t

FormalityCore.js (from c65b846) errors with

Type-checking endo2.fmc:
Boole : Type
ff    : error
tt    : error

Found 2 type error(s):

Inside ff:
Found type... P(tt)
Instead of... P((t) => (f) => f)
With context:
- P : Boole -> Type
- t : P(ff)
- f : P(tt)
On line 4:
     5|   P(boole_value)
     6| 
     7| ff: Boole
     8|   <P> (t) (f) f
     9| 
    10| tt: Boole
    11|   <P> (t) (f) t
    12| 

Inside tt:
Found type... P(ff)
Instead of... P((t) => (f) => t)
With context:
- P : Boole -> Type
- t : P(ff)
- f : P(tt)
On line 7:
     8|   <P> (t) (f) f
     9| 
    10| tt: Boole
    11|   <P> (t) (f) t
    12| 

Seems like an issue with the JS typechecker, my FormalityCore.hs on c1add22 passes this fine (but the .hs could very well have bugs too)

@gabriel-barrett suggested that this could be related to an issue where

apply: <A: Type> -> <B: Type> -> A -> (A -> B) -> B
  <A> <B> (a) (f) f(a)

test: Nat -> (Nat -> Nat) -> Nat
  (n)(f)
  let Test = Nat
  apply<Nat><Test>(n)(f)

Nat: Type //prim//
  nat_value<P: Nat -> Type> ->
  (zero: P(zero)) ->
  (succ: (pred: Nat) -> P(succ(pred))) ->
  P(nat_value)

zero: Nat
  <> (z) (s) z

succ: Nat -> Nat
  (n)
  <> (z) (s) s(n)

errors with

apply: <A: Type> -> <B: Type> -> A -> (A -> B) -> B
  <A> <B> (a) (f) f(a)

test: Nat -> (Nat -> Nat) -> Nat
  (n)(f)
  let Test = Nat
  apply<Nat><Test>(n)(f)

Nat: Type //prim//
  nat_value<P: Nat -> Type> ->
  (zero: P(zero)) ->
  (succ: (pred: Nat) -> P(succ(pred))) ->
  P(nat_value)

zero: Nat
  <> (z) (s) z

succ: Nat -> Nat
  (n)
  <> (z) (s) s(n)

This also passes FormalityCore.hs without issue.

Furthermore, by generalizing Bool to 3 values instead of 2, I've found that the JS typechecker exhibits unusual behavior. For example

 Trit : Type
  trit_value<P: Trit -> Type> ->
  P(ttt) -> 
  P(fff) -> 
  P(unk) -> 
  P(trit_value)

ttt : Trit
  <P> (x0) (x1) (x2) x0

fff : Trit
  <P> (x0) (x1) (x2) x1

unk : Trit
  <P> (x0) (x1) (x2) x2

Works fine, but if we instead change the type to

Trit : Type
  trit_value<P: Trit -> Type> ->
  P(ttt) -> 
  P(unk) ->
  P(fff) ->  
  P(trit_value)

We error. However, doing

Trit : Type
  trit_value<P: Trit -> Type> ->
  P(ttt) -> 
  P(fff) -> 
  P(unk) -> 
  P(trit_value)

ttt : Trit
  <P> (x0) (x1) (x2) x0

fff : Trit
  <P> (x0) (x1) (x2) x0

unk : Trit
  <P> (x0) (x1) (x2) x2

works fine.

To further generalize, every mapping from a finite set to itself has a corresponding Self-Type encoding. For example, there are 4 functions from the 2 set to the 2 set:

0. {0,1} -> {0,0}
1. {0,1} -> {0,1} 
2. {0,1} -> {1,0}
3. {0,1} -> {1,1}

Bool encodes {0,1} -> {0,1} and Boole (defined above) encodes {0,1} -> {1,0}. A self-mapping in a finite set of n elements can be assigned a number by ordering the domain elements in ascending order and then interpreting their image as digits in base n. So we could call Bool function 2.1 (the function numbered 1 on a set of 2) and Boole, 2.2.
The various Trit encodings would be 3.5, 3.7 and 3.2 respectively.

I have written a program to generate all possible self-type encodings of these finite self-mappings (or endomorphisms, if we want to be categorical): https://gist.github.com/johnchandlerburnham/fe53c5702bca6f0925f344905e82c0b0

Using this program, FormalityCore.hs passes typechecking for all encodings (up to n == 5), but JS does the following:

  • 2.0, 2.1, 2.2 check, 2.3 fails
  • 3.2, 3.3, 3.4.3.5,3.8,3.13,3.14, 3.23 check, the rest fail
  • similar pattern for 4 and 5

It appears that functions that map to a single value all work, some of the ones that map to two values work, and all (or nearly all, there may be exceptions) that map to 3 or more fail.

To replicate these results, cal writeEndo 4 "endo4.fmc" or similar from the above file and then do

$ ./javascript/fmc.js endo3 | grep 'error' | less

to see the function numbers that error.

More a question than an issue

Reading through the docs, as far as I understood, the proofs work as long as there are no external function calls or deeper embedding in another host language, so ideally suited for isolated scenarios like ethereum etc...

This reminded me of the prerequisites of gremlin:

... Any programming language that supports function composition (e.g. fluent chaining) and function nesting (e.g. call stacks) can support Gremlin. ...

So maybe formality could be very useful to statically proof invariants of graph traversal algorithms, because there are only a handful of primitives of the property graph the program would reduce to?

What do you think? Thanks for the interesting work!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.