Created
January 10, 2023 21:21
-
-
Save sgoguen/46200419194eb29b82079b0804e58c34 to your computer and use it in GitHub Desktop.
Converting Natural Numbers to Recursive Data Structures with Active Patterns
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module NatPatterns = | |
type nat = int64 | |
let nat(x) = int64(x) | |
// Rosenberg-Strong unpairing function (balances better than Cantor's) | |
// A true total function when you replace int64 with bigint | |
let unpair(z: nat): (nat * nat) = | |
let m = nat(floor (sqrt (double z))) | |
let ml = z - (m * m) | |
if ml < m then (ml, m) | |
else (m, (m * m) + m + m - z) | |
// Our unpairing function makes for a nice total active pattern | |
let (|Pair|) z = unpair z | |
// An AP for alternating between partitions | |
let inline (|Part1of2|Part2of2|) z = | |
if z % 2 = 0 then Part1of2(z / 2) else Part2of2(z / 2) | |
// An AP for alternating between 3 paritions | |
let inline (|Part1of3|Part2of3|Part3of3|) z = | |
match z % 3L with | |
| 0L -> Part1of3(z / 3L) | |
| 1L -> Part2of3(z / 3L) | |
| _ -> Part3of3(z / 3L) | |
// Here we're going to partition based on some type of max value | |
type NHead = Head of nat | Tail of nat | |
let takeMax m = function | |
| n when n <= m -> Head(n) | |
| n -> Tail(n - (m + 1L)) | |
module NatPatternExamples = | |
open NatPatterns | |
type BTree<'a> = | |
| Empty | |
| Node of Value:'a * Left:BTree<'a> * Right:BTree<'a> | |
// Can we construct an AP that converts an integer into a binary-tree? | |
let rec (|BTree|) = takeMax 0 >> function | |
| Head (n) -> Empty | |
| Tail (Pair(value, Pair(BTree (left), BTree (right)))) -> Node(value, left, right) | |
// Let's convert an int64 into a BTree | |
let (BTree(t)) = 10 | |
printfn "Tree: %A" t | |
type Expr<'a> = | |
| Var of 'a | |
| Lam of 'a * Expr<'a> | |
| App of Expr<'a> * Expr<'a> | |
// Let's convert an int64 into a Lambda expression | |
let rec (|Expr|) = function | |
| Part1of3(n) -> Var(n) | |
| Part2of3(Pair(v, Expr(e))) -> Lam(v, e) | |
| Part3of3(Pair(Expr(e1), Expr(e2))) -> App(e1, e2) | |
let (Expr(e)) = 10 | |
printfn "Expression: %A" e | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Your bijection works but has exponential overhead for common terms like Church numerals,
whose nested applications have one side of always constant size. Allow me to suggest a more balanced approach.
The number of closed terms of size n bits in a simple encoding is given by https://oeis.org/A114852 using the formula
which suggests the following total ordering of closed terms, equivalent to a bijection:
with output
The index of any term in this sequence, written in binary, is always less than its simple binary encoding. For example, Church numeral 5 = L (L (A (V 1) (A (V 1) (A (V 1) (A (V 1) (A (V 1) (V 0))))))))
appears at index 8194786 in this list, while its simple encoding is
00 00 01 110 01 110 01 110 01 110 01 110 10 corresponding to 2208098105.
This is because the simple encoding also leaves room for encoding open terms.