mlb2251 / stitch

A scalable abstraction learning library
MIT License
72 stars 8 forks source link

Preserve Beta-Normal Eta-Long Form #60

Open mlb2251 opened 2 years ago

mlb2251 commented 2 years ago

Beta-normal eta-long form

This is the form that dreamcoder programs are in.

If you're familiar with dreamcoders topdown search that's an easy way to think about this. Since it never produces unapplied lambdas it is clearly maintains beta-normal form, and since it autofills lambdas whenever it encounters an arrow-typed hole this maintains eta-long form.

Maintaining eta-long form during compression

Todo think about:

mlb2251 commented 2 years ago

Not urgent since Gabe is doing eta-expansion on their end, however we absolutely do want this for dreamcoder-identicalness

mlb2251 commented 2 years ago

Studying the current cases where dreamcoder fails in EtaLongVisitor:

Here's a crash example:

from dreamcoder.program import Program
from dreamcoder.domains.logo.logoPrimitives import turtle
from dreamcoder.type import arrow
from dreamcoder.program import EtaLongVisitor
EtaLongVisitor(request=arrow(turtle,turtle)).execute(Program.parse('(lambda (#(lambda (lambda ($1 (lambda (#(lambda (logo_PT (lambda (logo_FWRT $1 logo_ZA $0)))) logo_UL ($1 (#(lambda (logo_PT (lambda (logo_FWRT $1 logo_ZA $0)))) logo_UL (#(logo_FWRT logo_UL logo_ZA) $0)))))))) (#(lambda (lambda (logo_forLoop $1 (lambda (lambda (logo_GETSET $2 (logo_FWRT logo_ZL (logo_DIVA logo_UA $3) $0))))))) 6) (#(lambda (#(lambda (lambda (logo_forLoop $1 (lambda (lambda (logo_FWRT (logo_MULL logo_UL $2) (logo_DIVA logo_UA $3) $0)))))) $0 1)) 5) $0))'))

It turns out the line self.context.unify(request, ft.returns()) in EtaLongVisitor is to blame. This is because ft actually returns a t0 which can (and should in this case) actually become an arrow type turtle -> turtle which effectively adds one extra argument. However, this line unifies t0 with the request (which at this point in the recursion is just turtle) forcing t0=turtle.

So more generally I believe that this means when you try to unify some function type with its list of arguments you should from left to right across the arguments because things like t0 can expand into arrow types that actually add more arguments as you go! So unifying directly with the return type instead of traversing across the list of arguments can fail in these cases.

The solution is to add some code right before self.context.unify(request, ft.returns()) that does:

for i,x in enumerate(xs):
    self.context.unify(
        ft.functionArguments()[i],
        x.infer().instantiateMutable(self.context))
    ft = ft.applyMutable(self.context)

I'd love someone to review this because I have not actually worked with applyMutable() instantiateMutable() or unify() before.

I'll push this to the laps repo

mlb2251 commented 2 years ago

de-escalating from critical. Might be nice to add but is nontrivialded

mlb2251 commented 2 years ago

So more generally I believe that this means when you try to unify some function type with its list of arguments you should from left to right across the arguments because things like t0 can expand into arrow types that actually add more arguments as you go! So unifying directly with the return type instead of traversing across the list of arguments can fail in these cases.

Actually this is not correct. Well, it's correct except that if you think about how top down synthesis works you always unify the RETURN type of each primitive with your hole, so you DO always unify the return type and you never go left to right across the arguments.

mlb2251 commented 1 year ago

This should be considerably easier now that we have types :) we just need to include the types in compression ofc

mlb2251 commented 1 year ago

see also my notes 2023-01-09 adding eta expansion to stitch