Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested definitions #349

Closed
xsebek opened this issue Jun 2, 2022 · 5 comments
Closed

Nested definitions #349

xsebek opened this issue Jun 2, 2022 · 5 comments
Labels
C-Moderate Effort Should take a moderate amount of time to address. L-Language design Issues relating to the overall design of the Swarm language. S-Nice to have The bug fix or feature would be nice but doesn't currently have much negative impact. Z-Feature A new feature to be added to the game.

Comments

@xsebek
Copy link
Member

xsebek commented Jun 2, 2022

Feature

I would like to def inside def. It seems well defined in that def-end would nest properly and replace previous definitions.

Example

For example, if I wanted to avoid coming up with variable names for built robots, I could create a registry function:

def i = 0 end
def registry : int -> robot = \i. error "no such robot in registry" end

Building a registered robot is as easy as:

r <- build {}; def i'=i end; def i=i+1 end def r'=registry end; def registry=\j. if (j == i) {r} {r' i} end

Which would be nice to wrap in an outer definition:

def builder: {cmd a} -> cmd robot =  \cmd.
  r <- build cmd; def i'=i end; def i=i+1 end def r'=registry end; def registry=\j. if (j == i) {r} {r' i} end
end
// Definitions may only be at the top level: def i' = i end

Solution

I can imagine running a cmd could modify the outer definitions. The inner defs would need to be collected in infer and then put into the outer context when the outer def is called:

def i = 0 end
def f = def i = i + 1 end; end
// i is still 0
f
// i is now 1

Not sure if it would be necessary to store the outer def parameters somewhere so the inner defs could still access them.

I have a feeling this is Pandora's box with many nasty bugs that could occur when the inner context calls some forgotten variable, but it could be fun to have self-modifying programs. 😂

Alternatives

  • Lists or maps (Recursive types #154) would be a cleaner way to store robots.
  • Macro functions could do the job. 😈
@xsebek xsebek added Z-Feature A new feature to be added to the game. C-Moderate Effort Should take a moderate amount of time to address. S-Nice to have The bug fix or feature would be nice but doesn't currently have much negative impact. L-Language design Issues relating to the overall design of the Swarm language. labels Jun 2, 2022
@byorgey
Copy link
Member

byorgey commented Jun 2, 2022

Hmm, I totally understand what you're trying to do here, and the example with making a registry is a nice example. However, I'm not sure that nested definitions is the way to accomplish this. Essentially it seems like you're trying to use variables created via def as if they are global, mutable variables, but they're really not. In particular, although you can shadow the definition of registry with a new definition, any previously defined things which refer to registry will still see the value it had at the time they were defined (even if their evaluation is delayed until later). Once you had registry being updated via builder, you might want to also encapsulate other functions that referred to registry, but this would not work, since they would never see any updates to it; you would only be able to use registry directly at the REPL.

I think the better way to accomplish this would be through the use of real mutable references, which would not be hard to add, given that we already use a CESK machine (I thought we already had an issue for that but perhaps not).

The reason we don't allow nested def is because... well, actually, I'm not sure I completely remember the reasons. It might have to do with making it easier to do capability inference (although that is still kind of broken anyway). If I remember more about why that choice was made I'll update this issue.

@xsebek
Copy link
Member Author

xsebek commented Jun 3, 2022

Regarding the example, I think it is better for builder function to return a pair of current index and mapping:

def builder : (int * (int -> robot)) -> {cmd a} -> cmd (int * (int -> robot)) = \robM. \comm.
  r <- build comm;
  let i : int = fst robM in
  let m : int -> robot = snd robM in
  return (1+i, \j. if (j == i) {r} {m j});
end;
> def register : int -> robot = \i. error ("No registered robot with id " ++ format i) end;
> rM <- return (0, register);
(0, \i. error ("No registered robot with id " ++ format i)) : int * (int -> robot)
> rM <- builder rM {move; move};
(1, let i = 0 in let m = \i. error ("No registered robot with id " ++ format i) in let r = <r2> in \j. if (j == i) {r} {m j}) : int * (int -> robot)
> snd rM 0
<r2> : robot

I run into #333 trying to use the readable definition from a file and into #351 when I did not constrain the types. 🐛

But with some copy-pasting, it works! 🥳

I am missing devices required for '+' though...

@xsebek
Copy link
Member Author

xsebek commented Jun 9, 2022

Just to link it here, in #373 I encountered the following note in build step:

        -- Note that _capCtx must be empty: at least at the
        -- moment, definitions are only allowed at the top level,
        -- so there can't be any inside the argument to build.
        -- (Though perhaps there is an argument that this ought to
        -- be relaxed specifically in the case of 'Build'.
        let (caps, _capCtx) = requiredCaps (r ^. robotContext . defCaps) cmd

I wonder if that would be harder or simpler than nested definitions, but definitions in build and reprogram sound useful 🙂

But as it can be worked around, we can leave it for later.

@byorgey byorgey mentioned this issue Aug 10, 2022
@byorgey
Copy link
Member

byorgey commented Aug 11, 2022

Although nested definitions wouldn't really help with the original motivation written here, nested definitions are still a sebsible idea. To allow them we would have to first implement #636 and possibly #231 .

@xsebek
Copy link
Member Author

xsebek commented Nov 1, 2022

I no longer think this is useful - the original example can be implemented more cleanly in other ways.

A def inside build does not give any extra power than let, because once the robot stops, its definitions are no longer useful.

@xsebek xsebek closed this as completed Nov 1, 2022
mergify bot pushed a commit that referenced this issue Jun 19, 2024
…tate (#1928)

The big idea of this PR is to add a new type of CESK machine state, `Suspended`, which is a state the base robot automatically goes into after completing execution of a program entered at the REPL.  It is as if the base robot is still within the local context of any definitions etc. entered previously, just printed out an "intermediate result", and is now waiting to find out what the next term to be executed will be.  This allows us to treat `def` as syntax sugar for `let` and results in a much cleaner way to manage the context of definitions, which in turn allows us to remove a whole lot of special cases and fixes several annoying bugs.  See #1087 for more explanation and discussion.

Things that are **deleted** (!) by this PR:
- `TDef` (`def` is now just syntax sugar for `let`) (#997)
- `VResult` (we no longer need to return anything other than plain values)
- `FUnionEnv`, `FDiscardEnv`, `FLoadEnv` (we no longer need machinery to collect up definitions while evaluating terms)
- `ProcessedTerm` (just a plain `TSyntax` (i.e. `Syntax' Polytype`) is now enough)
- `Module` (it only existed to package up some contexts with typechecked terms, but we don't need them anymore)
- `RobotContext` and `topContext` (we don't need to store those things separately any more, they are just stored in the `CESK` machine where they belong)

Additions/changes:
- The `Requirement` module is split into `Requirements.Type` and `Requirements.Analysis` to avoid circular module imports
- New `Suspended` state for the CESK machine
- `def` and `tydef` are now allowed anywhere (even nested inside other definitions etc.) since `def x = y end; z` is just syntax sugar for `let x = y in z` (see #349)
- Code size decreased slightly for many programs using `def`, since `def` is now a synonym for `let`, and consecutive `def`s therefore do not require a `bind`.

Closes #1087.  Fixes #681 (hence also #736, #1796). Fixes #1032. Closes #1047. Closes #997. Fixes #1900. Fixes #1466. Fixes #1424.

See also #636.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-Moderate Effort Should take a moderate amount of time to address. L-Language design Issues relating to the overall design of the Swarm language. S-Nice to have The bug fix or feature would be nice but doesn't currently have much negative impact. Z-Feature A new feature to be added to the game.
Projects
None yet
Development

No branches or pull requests

2 participants