{"id":17796364,"url":"https://github.com/d-plaindoux/tyasta","last_synced_at":"2026-02-06T21:32:54.341Z","repository":{"id":142087938,"uuid":"405823433","full_name":"d-plaindoux/tyasta","owner":"d-plaindoux","description":"A journey with F*","archived":false,"fork":false,"pushed_at":"2023-05-22T06:05:17.000Z","size":55,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-02T08:26:29.836Z","etag":null,"topics":["dependent-types","paper-implementations","proof","type-checking"],"latest_commit_sha":null,"homepage":"","language":"F*","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/d-plaindoux.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-09-13T03:43:22.000Z","updated_at":"2025-02-10T01:59:22.000Z","dependencies_parsed_at":null,"dependency_job_id":"d232b02a-bcab-471d-9842-2f3ee9668e2f","html_url":"https://github.com/d-plaindoux/tyasta","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/d-plaindoux/tyasta","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-plaindoux%2Ftyasta","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-plaindoux%2Ftyasta/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-plaindoux%2Ftyasta/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-plaindoux%2Ftyasta/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/d-plaindoux","download_url":"https://codeload.github.com/d-plaindoux/tyasta/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-plaindoux%2Ftyasta/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29177551,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-06T20:14:21.878Z","status":"ssl_error","status_checked_at":"2026-02-06T20:14:21.443Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependent-types","paper-implementations","proof","type-checking"],"created_at":"2024-10-27T11:45:11.658Z","updated_at":"2026-02-06T21:32:54.325Z","avatar_url":"https://github.com/d-plaindoux.png","language":"F*","readme":"# Tyasta: A journey with F*\n\n[tyasta- Q. verb. to (put to the) test, *verify](https://www.elfdict.com/w/verify?include_old=1)\n\n## Introduction\n\n[A tutorial implementation of a dependently typed lambda calculus](https://www.andres-loeh.de/LambdaPi/LambdaPi.pdf)\n\n## Day 1: Simply Typed Lambda Calculus\n\n### 1am: Desiging the term algebra\n\nThe first difference with the original design suggested in the paper is the term algebra. I decide to do it using \na GADT instead of two separated ADTs.\n\n```f*\ntype infer : Type = \n    | Infer     : infer\n\ntype check : Type = \n    | Check     : check\n\ntype term : Type -\u003e Type = \n    | Annoted   : term check -\u003e typeL -\u003e term infer\n    | Bound     : nat -\u003e term infer\n    | Free      : name -\u003e term infer\n    | Apply     : term infer -\u003e term check -\u003e term infer\n    | Inferable : term infer -\u003e term check\n    | Lambda    : term check -\u003e term check\n    \nand typeL : Type = ...\n```\n\nThen we are able to a generalized `size` and `subst`.\n\n```f*\nval size : (#a:Type) -\u003e term a -\u003e nat\nlet rec size = function\n    | Annoted e t -\u003e 1 + length e\n    | Bound j     -\u003e 1\n    | Free x      -\u003e 1\n    | Apply e1 e2 -\u003e length e1 + length e2\n    | Inferable e -\u003e 1 + length e\n    | Lambda e    -\u003e 1 + length e\n```\n\nThe same design can be applied to the substitution function.\n\n### 2am: Type checker termination\n\nDuring this first day the main problem I'm facing the proof termination of `typeCheck` and `typeInfer`.\nThis is actually normal when we take a closer look at the abstraction type verification code:\n\n```haskell\ntype↓ i G (Lam e) (Fun τ τ′)\n  =type↓ (i+1) ((Local i,HasType τ):G) (subst↓ 0 (Free (Local i)) e) τ′\n```\nThe system is not able to check if the term `(subst↓ 0 (Free (Local i)) e)` decreases. This can be simply solved \nusing our metric dedicated to the term algebra: `size`. Thanks to this metric we can see the size of `Free (Local i)` and\n`Bound _` are the same. So we can \"easily\" define a lemma in this case:\n\n```f*\nval subst_constant : \n        #a:Type -\u003e\n        i:nat -\u003e \n        r:(term infer){size r = 1} -\u003e\n        e:term a -\u003e\n        Lemma (ensures\n            (let e' = subst i r e in\n                 size e' = size e\n            )\n        )\n        (decreases e)\n        [SMTPat (subst i r e)]\n```\n\nThis lemma says: if we replace a bound expression - of size 1 - with a term of size 1 then the size of the initial term \nand the size of the computed term are equals. In addition we specify the decreased term and finally we explicit to the \nSTM solver the pattern `subst i r e` i.e. `[SMTPat (subst i r e)]` to be used when the termination proof should be done.\n\nNow we are ready to prove the termination!\n\n```f*\nlet rec typeInfer i g = function\n    ...\n    \nand typeCheck i g e t =\n    match e with\n    | Inferable e -\u003e \n        constant () \u003c$\u003e unless (typeInfer i g e) ((=) t) (throwError \"type mismatch\")\n    | Lambda e    -\u003e \n        (match t with\n        | Function t t' -\u003e \n            let r  = Free (Local i) in\n            (* This assert is used by the SMT solver in order to apply the lemma *)\n            assert (size r = 1); \n            typeCheck (i + 1) ((Local i, HasType t) :: g) (subst 0 r e) t'\n        | _ -\u003e \n            throwError \"type mismatch\")    \n```\n\nQED.\n\nNote: The assert can be replaced by the application of the lemma via `subst_constant i r e`.\n\n### 3am: Open and Closed terms\n\nIn the general design, manipulated terms are closed. A closed term has no free \nvariable i.e. each De Bruijn indice corresponds to a level of enclosing lambda. \nNeverthless, with the current abstract syntax we can build terms like:\n\n```f*\nlet ex = Lambda (Inferable (Bound 4))\n```\n\nThen type checking such term should leads to an unbound variable error. In the paper we \ncan see that such case is missing as expressed page 1010: \"The type checker will never \nencounter a bound variable; correspondingly the function type↑ has no case for Bound\".\n\nWell that's fine but in F* we cannot remove such pattern matching or we have to prove \nthat such case never occurs!\n\nIn order to solve this problem we should observe how the type checker works. Each `Bound`\nterm is replaced by a `Free (Local i)`. Based on this we can define a function deciding\nif a given term is closed or not using the same technic e.g the substitution in order to \neliminate such `Bound` terms. \n\n```f*\nval closed  : #a:Type -\u003e nat -\u003e e:term a -\u003e Tot bool (decreases (size e))\n\nlet rec closed i = function\n    | Annoted e t -\u003e closed i e\n    | Bound j     -\u003e false\n    | Free x      -\u003e true\n    | Apply e1 e2 -\u003e closed i e1 \u0026\u0026 closed i e2\n    | Inferable e -\u003e closed i e\n    | Lambda e    -\u003e let r  = Free (Local i) in\n                     assert (size r = 1);\n                     closed (i+1) (subst 0 r e)\n```\n\nThis `closed` predicate uses the same pattern when managing a `Lambda e` i.e. it creates \na term for the substitution and eliminates the corresponding `Bound`. Then we can provide\nrefined types in the type checker signatures using such predicate:\n\n```f*\nval typeInfer   : n:nat -\u003e context -\u003e e:(term infer){closed n e} -\u003e Tot (result typeL) (decreases (size e))\nval typeCheck   : n:nat -\u003e context -\u003e e:(term check){closed n e} -\u003e t:typeL -\u003e Tot (result unit) (decreases %[size e;t])\nval typeInfer0  : context -\u003e e:(term infer){closed 0 e} -\u003e result typeL\n```\n\nFinally we can remove the pattern matching dedicated to `Bound` term because the term is `closed`.\n\nQED.\n\n### 4am: Coinductive types\n\nIn the original paper, the evaluation of a given term produces a value:\n\n```haskell\ndata Value \n    = VLam (Value → Value)\n    | VNeutral Neutral\n\ndata Neutral\n    = NFree Name\n    | NApp Neutral Value\n```\n\nOnce again we can use a GADT instead of two ATDs.\n\n```fstar\ntype value : Type =\n    | Value : value\n\ntype neutral : Type =\n    | Neutral : neutral\n\ntype vterm      : Type -\u003e Type =\n    | VLam      : (vterm value -\u003e vterm value) -\u003e vterm value\n    | VNeutral  : vterm neutral -\u003e vterm value\n    | NFree     : name -\u003e vterm neutral\n    | NApp      : vterm neutral -\u003e vterm value -\u003e vterm neutral\n```\n\nIn this design `vterm` is not an inductive type but a coinductive one (cf. the constructor `VLamb`). If fact, the evaluation\nof a `Lamb` term is given by a reified function in the host langage:\n\n```haskell\neval↓ (Lam e) d = VLam (λx → eval↓ e (x:d))\n```\n\nIn fact, the function delays the evaluation of the internal term by pushing the captured value above the captured environment. Unfortunately, F* does not have support for coinductive types. In such case, we have to replace the `VLamb` construction with another. This can be done easily by introducing the closure that captures the inner element and the current environment.\n\n```fstar\ntype vterm      : Type -\u003e Type =\n    | VClosure  : term check -\u003e env -\u003e vterm value\n    | ...\n\nand env         : Type = list (vterm value)\n```\n\n### 5am: Evaluation and termination\n\nIt's time now to propose an evaluation for the interpretation of lambda expressions. Unfortunately, we cannot prove that\nsuch operation always terminates and this is clear when we have a look at the implementation of the evaluation.\n\n```haskell\neval↑ (e :@: e′) d = vapp (eval↑ e d) (eval↓ e′ d)\n```\n\nIn fact, the evaluation of `e` (or `e′`) returns a result with an unknown size. For instance, we can have a divergent program like:\n\n```ocaml\nlet rec f x = f x\n```\n\nwhich never terminates. So the effect linked to the result type is `Dv` (for divergent) and this reflect a possible infinite computation \nand that's fine.\n\n### 6am: Evaluation and finite set\n\nTo be continued ...\n\n## Day 2: Dependent types\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd-plaindoux%2Ftyasta","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fd-plaindoux%2Ftyasta","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd-plaindoux%2Ftyasta/lists"}