{"id":15971820,"url":"https://github.com/kputnam/hee","last_synced_at":"2025-04-04T15:45:13.304Z","repository":{"id":137344039,"uuid":"474715796","full_name":"kputnam/hee","owner":"kputnam","description":"Statically-typed functional and concatenative programming language","archived":false,"fork":false,"pushed_at":"2022-04-13T19:49:56.000Z","size":2675,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-10T01:41:29.542Z","etag":null,"topics":["interpreter","programming-language","stack-language","type-checker"],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kputnam.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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":"2022-03-27T17:53:30.000Z","updated_at":"2023-12-29T18:34:50.000Z","dependencies_parsed_at":"2023-03-14T13:15:40.399Z","dependency_job_id":null,"html_url":"https://github.com/kputnam/hee","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kputnam%2Fhee","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kputnam%2Fhee/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kputnam%2Fhee/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kputnam%2Fhee/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kputnam","download_url":"https://codeload.github.com/kputnam/hee/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247208066,"owners_count":20901568,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["interpreter","programming-language","stack-language","type-checker"],"created_at":"2024-10-07T20:40:45.116Z","updated_at":"2025-04-04T15:45:13.281Z","avatar_url":"https://github.com/kputnam.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bumbling about Pointless Programming\n\nPoint-free style is a paradigm in which function definitions do not include\ninformation about its arguments. Instead, functions are defined in terms of\ncombinators and composition (Wikipedia). **Hee** is a concatenative, functional\nprogramming language built for no practical purpose -- my goal is to use it as\na vehicle for understanding PL topics.\n\n## Development Status [![Build Status](https://secure.travis-ci.org/kputnam/hee.png)](http://travis-ci.org/kputnam/hee)\n\n**Hee** is in the very early stages of development. Some aspects of the syntax\nhaven't settled enough to allow providing examples. This includes the type\nsyntax, comments, function declarations, and type declarations. The general\ngoal is to keep the concrete syntax as minimal as possible, so I'm still\nlooking for ways to implement these features without adding \"extra\" syntax.\n\nThe preliminary type system is not usable. Several issues must be addressed\nbefore certain simple terms can be correctly typed. Some terms that pose\ninteresting problems are:\n\n* `dup compose`, because `A (B → B) → A (B → B)` is not sufficiently general.\n  This is the type inferred by HM for the analogous lambda calculus expression\n  `λf x. f (f x)`. We should be able to derive a principle type for it that\n  admits expressions like `[+] dup compose` and `[33] dup compose`.\n\n* `dup` itself which seems to break concatenativity without impredicative\n  polymorphism. That is, `[id] [id]` has the type `(∀T. T → T) (∀U. U → U)`,\n  so we should assign `[id] dup` the same type. Without impredicative\n  polymorphism we will derive `∀T. (T → T) (T → T)` instead.\n\n* `dup unquote`, the U-combinator, requires recursive types. This permits\n  general recursion.\n\n### Current\n\nI'm starting from a clean slate in Haskell. Prerequisites include ghc 7.4 and\nhaskell-platform 2012.\n\n    $ git clone git://github.com/kputnam/hee.git\n    $ cd hee\n    $ cabal install\n\n#### Writing a small program\n\n    $ cat examples/test.hee\n    increment\n      : num -\u003e num\n      \" doc string follows type (and is optional)\n      = 1 +\n\n    decrement\n      \" types declarations are optional\n      = 1 -\n\n    main\n      = 10\n          increment\n          decrement\n        10 ==\n\n#### Printing parse the tree\n\n    $ cat examples/test.hee | hee-parse\n    [DNameBind \"inc\"\n      (Just \"num -\u003e num\")\n      (Just \" doc string follows type (and is optional)\\n\")\n      (ECompose (ELiteral (LInteger Decimal 1)) (EName \"+\"))\n    ,DNameBind \"decrement\"\n      (Just \" type declarations are optional\\n\")\n      (ECompose (ELiteral (LInteger Decimal 1)) (EName \"-\"))\n    ,DNameBind \"main\"\n      Nothing\n      Nothing\n      (ECompose\n        (ELiteral (LInteger Decimal 10))\n        (ECompose\n          (EName \"increment\")\n          (ECompose\n            (EName \"decrement\")\n            (ECompose\n              (ELiteral (LInteger Decimal 10))\n              (EName \"==\")))))]\n\n#### Evaluating a program\n\nPrograms must be a set of top-level declarations (no top-level expressions\nwill be parsed). The `main` definition will be executed.\n\n    $ echo 'main = 5 [swap dup 1 \u003c= [pop pop 1] [dup 1 - dig u *] if] u' | hee-eval\n    120\n\nNote that type declarations are currently ignored and there are no static\ntype checks.\n\n#### Running tests\n\n    $ cabal configure --enable-tests\n    $ cabal build\n    $ cabal test\n\n### Attic\n\nThere is a type checker that serves as a proof of concept in\n[`attic/src/main/haskell`](hee/tree/master/attic/src/main/haskell). You can\ninfer the type of expressions like so:\n\n    attic$ rake hee:check 'swap cons swap cons swap cons'\n    \"swap cons swap cons swap cons\"\n    \"(A a a a ([] a) -\u003e A ([] a))\"\n\nThe above type states, given a stack with three elements (of the same type `a`)\nand a list of elements, the expression will produce a list of `a` elements.\n\nThere's a REPL written in Ruby, in [`attic/bin/bee.rb`](hee/blob/master/attic/bin/bee.rb).\nThis includes a few features like tab-completion, execution traces, and the\nability to save definitions created in the REPL to an external file. The\ninterpreter achieves tail-call optimization easily because it effectively\nimplements [subroutine threading](http://en.wikipedia.org/wiki/Threaded_code#Subroutine_threading).\n\n![Screenshot](https://raw.github.com/kputnam/hee/master/attic/repl.png)\n\nThere is a small runtime library in [`attic/runtime`](hee/blob/master/attic/runtime)\ndirectory that is loaded when the REPL starts. Mostly this includes some type\ndefinitions, like lists and booleans with a number of functions to operate on\nthese types. These files include what *appears* to be module declarations and\ncomments, however these are parsed as top-level expressions which are discarded\nby the parser. The parser only reads *definitions* from files.\n\n### Goals\n\nMy primary motivations for developing **hee** are to gain a deeper\ntheoretical and practical understanding of programming languages and type\nsystems. I am less concerned with developing a practically *usable* language.\n\nFor example, one motivation behind using postfix syntax is it is simple to\nparse, though may be harder for humans to read and write. Using point-free\nnotation means the symbol table doesn't need to maintain information about\nthe current scope: there are no \"local variables\". These kinds of choices\nsimplify the language implementation, and may (or may not) yield benefits\nfor programmers using the language.\n\nSome features I'd like to explore include:\n\n* Module systems\n* Type inference\n* Quasiquotation\n* Interactive development\n* Comprehensible type errors\n\n## Syntax\n\nLike many stack-based languages, **hee** uses an postfix syntax for expression.\nOperands are written before operators, e.g. `1 3 +`.\n\n### Kinds\n\n    κ,ι ::= @       -- stack\n          | *       -- manifest\n          | κ → ι   -- function\n\nKinds classify types. **Hee** distinguishes stack-types, value-types, and type\nconstructors from one another.\n\n### Types\n\nStacks (rows), with kind `@`\n\n    σ,φ ::= ∅       -- empty\n          | S,T     -- type variable\n          | σ τ     -- non-empty stack\n\nValues, with kind `*`\n\n    τ,υ ::= a,b     -- type variable\n          | σ → φ   -- function on stacks\n          | int\n          | str\n          | ...\n\nCurrently the type system is too under-powered to be useful. Several features\nlike quantified types, qualified types, type constructors, type classes, and\nhigher-rank polymorphism are intended, once I understand them better.\n\n### Terms\n\n    e,f ::= ∅         -- empty (identity)\n          | e f       -- composition\n          | [e]       -- abstraction\n          | name      -- top-level definition\n          | literal   -- char, num, str, etc\n\nUnlike an applicative language, terms are built entirely by function composition.\nFor instance, `+ *` composes an addition with a multiplication. Similarly, `1 +`\ncomposes a function `1` that pushes the numeric value one on the stack with a\nfunction `+` that pops the top two stack elements and pushes their sum.\n\n## Minimal Combinators\n\nThese combinators are used to manipulate abstractions (function values). Here\nare some of the type signatures:\n\n    quote\n      : S a → S (T → T a)\n      \" convert a value to an abstraction that yields that value\n\n    compose\n      : S (T → U) (U → V) → S (T → V)\n      \" composes two abstractions, eg [3] [4] compose ==\u003e [3 4]\n\n    unquote\n      : S (S → T) → T\n      \" applies an abstraction, eg [3] unquote ==\u003e 3\n\n    dip\n      : S a (S → T) → T a\n      \" applies an abstraction, eg 4 [3] dip ==\u003e 3 4\n\nBecause arguments aren't explicitly named, they must be accessed according to\ntheir position on the stack. Several stack-shuffling combinators are provided\nto move values around. Below are type signatures of some of these:\n\n    pop  : S a → S\n    dup  : S a → S a a\n    swap : S a b → S b a\n    over : S a b → S a b a\n    dig  : S a b c → S b c a\n\n## Algebraic Data Types\n\nDeclaring an algebraic data type implicitly creates a deconstructor. This is a\nfunction which takes function arguments corresponding to each constructor, in\nthe same order as the constructor definitions. For instance, `true [\"T\"] [\"F\"]\nunboolean` yields `\"T\"`. Note `if` is nothing more than `unboolean`.\n\n    :: boolean\n     | true\n     | false\n     ;\n\n    -- unboolean is created implicitly\n    -- unboolean : S boolean (S → T) (S → T) → T\n\nBoolean operators can be implemented like so:\n\n    : not [false] [true]    unboolean ;\n    : and [id] [pop false]  unboolean ;\n    : or  [pop true] [id]   unboolean ;\n\nLike other languages with algebraic data types, each constructor can wrap any\nfixed number of values. The current notation is inadequate for type checking,\nand the field names are ignored -- this will change.\n\n    :: list\n     | null\n     | cons tail head\n     ;\n\n    -- unlist is created implicitly\n    -- unlist : S a-list (S → T) (S a-list a → T) → T\n\n## Exploiting Multiple Return Values\n\nIn most non stack-based languages, functions can return at most one value.\nMultiple values can be simulated by packing them into a single value, and\nthen unpacking them in the caller.\n\n    nextFree :: Int → [Int] → (Int, [Int])\n    nextFree current []     = (current+1, [])\n    nextFree current (v:vs) = if current+1 \u003c v\n                              then (current+1, v:vs)\n                              else nextFree v vs\n\nIn a stack-based language, \"functions\" can return any number of values,\nincluding zero, by pushing them onto the stack. For example, the `next-free`\nfunction, given a sorted list of bound ids and a starting point, shrinks\nthe list of bound ids that need to searched on the next call, and also\nreturns the next free id.\n\n    : next-free               -- S num-list num → S num-list num\n      swap                    -- id xs\n        [1 + null swap]       --   id → null id'\n        [dig 1 + 2dup \u003e       --   id xs' x → xs' x id' → xs' x id' bool\n            [[cons] dip]      --     xs' x id' → xs id'\n            [pop next-free]   --     xs' x id' → ...\n          if]\n      unlist ;\n\nThis lets us call `next-free` like so:\n\n    -- define a list of integers: 2, 4, 5\n    : bound-vars null 5 cons 4 cons 2 cons ;\n\n    bound-vars -1     -- (2,4,5) -1\n      next-free       -- (2,4,5) 0\n      next-free       -- (2,4,5) 1\n      next-free       --   (4,5) 3\n      next-free       --      () 6\n      next-free       --      () 7\n\n## Life Without (Implicit) Closures\n\n**Hee** doesn't have lexical scopes, mutable bindings, or parameter names so\nclosures as we know them aren't meaningful. However, we can exploit other\nfeatures of the language to achieve similar results.\n\n    : generate-free'    -- xs x\n      next-free         -- xs' x'\n      swap quote        -- x' [xs']\n      over quote        -- x' [xs'] [x']\n      [generate-free']  -- x' [xs'] [x'] [generate-free']\n      compose compose ; -- x' [xs' x' generate-free']\n\nValues can be lifted to functions using `quote`, and functions can be composed\nwith `compose`. This enables immutable state to be encapsulated inside a function.\n\n    : generate-free\n      quote [-1 generate-free'] compose ;\n\nThese two features also enable partial application, e.g. `[-1 generate-free']`.\nUsing this technique we have hidden the list of unbound ids from the caller.\n\n    bound-vars        -- [2,4,5]\n      generate-free   -- [...]\n      unquote         -- 0 [...]\n      unquote         -- 0 1 [...]\n      unquote         -- 0 1 3 [...]\n      unquote         -- 0 1 3 6 [...]\n\nEach time the function is applied, it produces the next free variable and also\nproduces the next *function* which embeds any necessary state to generate the\nremaining free ids.\n\n## Typing Rules\n\nEach rule corresponds to one of the syntactical forms for terms\n\n\n    T-EMPTY   -----------\n               ∅ : S → S\n\n\n               e : S → T    f : T → U\n    T-COMPOSE ------------------------\n                    e f : S → U\n\n\n                    e : S → T\n    T-QUOTE   ---------------------\n               [e] : U → U (S → T)\n\n\n                Γ(name) = S → T\n    T-NAME    ------------------\n                 name : S → T\n\n\n    T-LITERAL ------------------------------------------------------------\n               1 : S → S int    'a : S → S char    \"x\" : S → S str    ...\n\n\nThe type context Γ is elided from most rules for brevity, and also because\nexpressions cannot extend it during computation. That is, only top-level\ndefinitions (not expressions) can bind values to names.\n\n### Example\n\nConsider the expression `swap compose unquote 1 +`. We'll perform type inference\non this expression by evaluating one type judgement at a time.\n\nFirst, `swap`. This is viewed as a composition with the empty term, so we'll\nuse `T-COMPOSE` to infer the type of `∅ swap`.\n\n      e : S → T       f :   T   →   U\n          -   -           .---.   .---.\n\n      ∅ : S → S    swap : A b c → A c b\n     ---------------------------------- T-COMPOSE\n           ∅ swap : A b c → A c b\n\n                   '----'   '---'\n              e f :  S    →   U\n\nSo the pre-conditions of `T-COMPOSE` are unified with the types of `∅` and\n`swap`. That is, `S = S`, `T = S`, `T = A b c`, and `U = A c b`. We perform\nunification on the two equations for `T`, which yields the substitution:\n\n    S = A b c\n\nWe apply this substitution to the post-condition of `T-COMPOSE`, which is\n`S → U`. This yields the type of `∅ swap : A b c → A c b`. This shows that\ncomposition with the empty term `∅` is unsurprisingly trivial.\n\nNow we compose `swap` with the term `compose`, which follows similar steps.\n\n              S   →   T                         T        →     U\n            .---.   .---.              .---------------.   .-------.\n\n     swap : A b c → A c b    compose : D (E → F) (F → G) → D (E → G)\n    ----------------------------------------------------------------- T-COMPOSE\n              swap compose : A (F → G) (E → F) → A (E → G)\n\n\nWe've unified both equations for `T` from the premise of the `T-COMPOSE`\nrule: `A c b` with `D (E → F) (F → G)`, resulting in the substitution:\n\n    c = E → F\n    b = F → G\n\nThen we can apply that substitution to `S → T`, the conclusion of `T-COMPOSE`,\nwhich gives us `swap compose : A (F → G) (E → F) → A (E → G)`.\n\nNext, compose the term `swap compose` with the term `unquote`:\n\n                            S         →     T                      T     → U\n                    .---------------.   .-------.              .-------.   -\n\n     swap compose : A (F → G) (E → F) → A (E → G)    unquote : H (H → I) → I\n    ----------------------------------------------------------------------- T-COMPOSE\n                  swap compose unquote : A (F → G) (A → F) → G\n\nHere we unify `A (E → G)` with `H (H → I)`, resulting in the substitution\n\n    I = G\n    H = E\n    H = A\n    E = A\n\nThis time, the constraints propagated *backward* to the input `S`. Now the\nfunction at the top of the stack must have the domain `A`, matching the stack\nbelow the second element. Previously, its domain was `E` which was unrelated\nto `A`.\n\nLastly, we compose the term `swap compose unquote` with the term `+`:\n\n                                    S         → T            T     →   U\n                            .---------------.   -        .-------.   .---.\n\n     swap compose unquote : A (F → G) (A → F) → G    + : K int int → K int\n    --------------------------------------------------------------------- T-COMPOSE\n         swap compose unquote + : A (F → K int int) (A → F) → K int\n\nLike before, we unify `G` with `K int int`, resulting in the substitution:\n\n    G = K int int\n\nThen we apply this substitution to `S → U`, which is `A (F → G) (A → F) → K int`\nin this case. Notice again that constraints propagated so we've restricted the\ntype of the input merely by composing with another term.\n\n## Related Links\n\n* [Why Concatenative Programming Matters](http://evincarofautumn.blogspot.com/2012/02/why-concatenative-programming-matters.html)\n* [The Joy Programming Language](http://www.latrobe.edu.au/phimvt/joy.html)\n* [Stack machine](http://en.wikipedia.org/wiki/Stack_machine)\n* [Stack-oriented programming language](http://en.wikipedia.org/Stack-oriented_programming_language)\n* [My History with Forth Stack Machines](http://www.yosefk.com/blog/my-history-with-forth-stack-machines.html)\n* [Understanding What It's Like to Program in Forth](http://prog21.dadgum.com/33.html)\n* [concatenative.org](http://concatenative.org/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkputnam%2Fhee","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkputnam%2Fhee","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkputnam%2Fhee/lists"}