Skip to content

Custom num-encodings create unexpected eagerness #110

Description

@Kacarott

Custom number encodings which use other compiled LC terms create awkward and unnecessary "layers of eagerness".

As an example, this code creates a dummy number encoding in which every number is represented as K. The K is compiled as LC to retain laziness, but due to the "layer of eagerness" caused by the wrapper function around K in Javascript, each argument to K is eagerly evaluated and so the test fails with an evaluation error.

import { assert, LC, getSolution } from "./lc-test.js";
const { K } = LC.compile(String.raw`
K = \ k _ . k
`);

const toInt = () => 0;
const fromInt = () => K;

LC.configure({ purity: "Let", numEncoding: {toInt, fromInt}, verbosity: "Concise" });
const { foo } = LC.compile(String.raw`
True = \ a _ . a
foo = 0 True ()
`);

describe("Example", () => {
  it("example tests", () => {
    assert.strictEqual( foo(true)(false), true );
  });
});

Potential fixes would be:
a) Improve the logic of fromInt to detect LC wrapper functions, and to use the underlying term directly (removing the eagerness layer)
b) Add an explicite alternative fromInt which is expected to return a LC term
c) Allow fromInt to accept strings, which it could compile itself into LC terms to use (??)

Activity

  1. JohanWiltink commented on Sep 4, 2025

    @JohanWiltink
    Collaborator

    Most elegant fix is exporting parse.

    Normal users don't need or want it, but defining custom numEncodings is arguably outside the scope of normal use anyway.

    One could, of course, get stuck in an endless loop when using numeric literals in source code for fromInt, but don't do that and you'll be fine. This is how fromInt is meant to function; using LC.compile for fromInt is a kludge that relies on feeding JS to evalLC, instead of AST. LC.parse has access to all the constructors it needs.

  2. JohanWiltink commented on Sep 16, 2025

    @JohanWiltink
    Collaborator

    I've tested it locally and it works.

    A custom fromInt would look somewhat like

    const fromInt = n =>
      LC.parse( "number = " + function go(n) {
                                if ( n )
                                  if ( n&1 )
                                    return String.raw`\ _zero _bit0 bit1 . bit1 ${ go(-(n>>1)) }`;
                                  else
                                    return String.raw`\ _zero bit0 _bit1 . bit0 ${ go(-(n>>1)) }`;
                                else
                                    return String.raw`\ zero _bit0 _bit1 . zero`;
                              } ( n ) ).getValue("number").term ;
  3. JohanWiltink commented on Sep 21, 2025

    @JohanWiltink
    Collaborator

    solved by #111. needs to be deployed yet, but the source tree now allows importing the parser. closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions