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

Scope composition #96

Open
asoffer opened this issue Dec 6, 2021 · 8 comments
Open

Scope composition #96

asoffer opened this issue Dec 6, 2021 · 8 comments

Comments

@asoffer
Copy link
Owner

asoffer commented Dec 6, 2021

There seems to be a somewhat common pattern with nesting scopes, for which I think the following example is illustrative:

file.With (filename) open [f: file.File] {
  file.Lines (f) each [line: []char] {
    ProcessLine(line)
  }
}

We've specified f but only use it to pass to the next scope. It would be nice if we could compose these scopes directly.

This proposal is for a non-overloadable operator (tentatively spelled >>) which can connect nested scopes, passing the arguments provided from the outer block directly as arguments to the inner scope. Rewritten, the above code would be:

file.With (filename) open >> file.Lines () each [line: []char] {
  ProcessLine(line)
}

There are a few caveats here:

  1. We need parentheses still between Lines and each to resolve parsing ambiguities. It also would allow us to feed further arguments if Lines had accepted any. So more carefully, the arguments provided from the open block are passed as a prefix of the arguments to Lines, and more can be placed in the parentheses.
  2. If a scope has multiple blocks, this doesn't compose super neatly. There's nowhere to place the file.With error scope anymore. We could place it before open, meaning this mechanism only works for the last block in the scope. This solution feel satisfying to me though.

It's worth mentioning what my Advent Of Code day 2 solution would look like. If we adopted this, and changed the parsing rules slightly to be smarter about newlines (I think this is possible, but I'm not 100% positive), the solution would look like:

file.With ("input.txt") open >> file.Lines () each >> parse_command ()
  forward [n: i64] { horizontal += n }
  down    [n: i64] { vertical += n }
  up      [n: i64] { vertical -= n }

Which I think is particularly elegant.

@asoffer
Copy link
Owner Author

asoffer commented Dec 6, 2021

It dawn's on me that we already support this when blocks have no arguments (this is how else-if chains work) so the extension is pretty feasible, even without the >> operator.

@wrhall
Copy link

wrhall commented Dec 6, 2021

If file.With returned a monad error eg optional<file.File>, you could maybe have a second scope that is unpack_or("...") which would not enter the scope (on that note, file could just do that, right?)

The second scope should be able to forward with >> to a third nested scope

@asoffer
Copy link
Owner Author

asoffer commented Dec 6, 2021

Could you provide a sample of what this would look like? I'm having trouble understanding.

@wrhall
Copy link

wrhall commented Dec 6, 2021

file.With ("input.txt") open >> monad.unpack >> file.Lines () each >> parse_command ()
  forward [n: i64] { horizontal += n }
  down    [n: i64] { vertical += n }
  up      [n: i64] { vertical -= n }

where you define Monad.unpack to short-circuit (e.g. like if false) if the optional is None

@wrhall
Copy link

wrhall commented Dec 6, 2021

This is (afaict) the? from Rust, except it maybe doesn't RETURN_IF (more like BREAK_IF)

@asoffer
Copy link
Owner Author

asoffer commented Dec 6, 2021

I see. Yes, you could do this, having the monad scope try to unwrap and enter the body if it succeeds and pass over if it fails. However, the file.With scope could do the same. In fact it already does if you don't provide an error handling scope. The question is, what do you do if you really do want a scope for error handling?

@wrhall
Copy link

wrhall commented Jan 5, 2022

Let me try again to understand the error issue.
We're going from this:

file.With (filename) open [f: file.File] {
  file.Lines (f) each [line: []char] {
    ProcessLine(line)
  }
} onError {
  // do something
}

to something like this

file.With (filename) open >> file.Lines () each [line: []char] {
  ProcessLine(line)
}  // where does onError go? This is the close curly brace of file.Lines

Let's remove some sugar, though.

file.With (filename) open >> {
  file.Lines () each [line: []char] {
    ProcessLine(line)
  }
} onError { ... }

That seems ok. We can even compress it back to 3 lines if we want

file.With (filename) open >> { file.Lines () each [line: []char] {
    ProcessLine(line)
} } onError { ... }

How do I feel about this? Effective Icarus now warns you about onError scope placement when using syntax sugar, but other than that ... fine?
The real issue is that we probably want something about our onError to redundantly confirm which scope it's holding errors for. And if you do that, you don't need the extra curly braces. And if you don't do that, you've made a bit of a confusing construct.

@wrhall
Copy link

wrhall commented Jan 5, 2022

All this said, I'm generally in favor of the >> syntax. I think you have lots of options for what this syntax looks like (within the confines of the language), so it doesn't have to be >>. But if that character is free it makes some amount of sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants