syntax icon indicating copy to clipboard operation
syntax copied to clipboard

Syntax sugar for open (a.k.a local open)

Open believer opened this issue 4 years ago • 25 comments

I'm receiving an error when locally opening a module that I'm not sure how to handle

4 │  Runtime.onInstalled(_ => {
5 │    DeclarativeContent.(
6 │      removeRules(_ => {
7 │        addRules([
8 │          {

I'm not sure what to parse here when looking at "(".

The error highlights removeRules

believer avatar Jul 02 '20 05:07 believer

There's currently no syntax sugar for local open. We're looking into a solution. For now you can use:

Runtime.onInstalled(_ => {
  open DeclarativeContent
})

IwanKaramazow avatar Jul 02 '20 19:07 IwanKaramazow

I think it might be great that there is no open sugar, as it's can be confusing, and making local opens more explicit isn't a bad thing imo.

jfrolich avatar Jul 03 '20 01:07 jfrolich

Ahh great, thanks @IwanKaramazow!

believer avatar Jul 03 '20 04:07 believer

I think it might be great that there is no open sugar

Disagree here.

alex35mil avatar Jul 03 '20 10:07 alex35mil

I would also be very happy if this would be possible:

let fn = () => Dom.{
  ...
}

alex35mil avatar Jul 03 '20 10:07 alex35mil

@alexfedoseev Yes, that's actually a valid use case. I would like to look into alternatives.

IwanKaramazow avatar Jul 03 '20 10:07 IwanKaramazow

Maybe we could take the old horrible JS equivalent-ish

with Cn ("one" + "two" + "three");

let fn = () => with Dom.Element {
  ...
}

bloodyowl avatar Jul 03 '20 19:07 bloodyowl

I always found the local open syntax strange (never seen something like that syntax before) and hard to understand when learning Reason and reading code snippets. I think something like @bloodyowl suggestion reads much better for newcomers.

jfrolich avatar Jul 04 '20 03:07 jfrolich

Local open adds parenses and indentation, overall quite a lot of noise in my opinion. I usually prefer to use open but refmt transforms it back to a local open 😑 Maybe we could do something like

open Belt, Dom, Element

If we want a terse syntax

tsnobip avatar Jul 08 '20 08:07 tsnobip

This really isn’t about terse vs. verbose open statements. It’s about being able to use multiple, mutually exclusive opens at the expression level vs. the scope level.

Local opens allow us to be much more explicit about precisely where each module is being used/opened.

The normal open keyword will open the module for the entire enclosing scope, which is not always desirable. E.g. you might not be fully aware of which Array module you are using, or the fact that your logging function is actually streaming data to cloud storage instead of to the console on your local machine.

The only other way to confine the scope of a normal open is to wrap your expression in a new block scope, and open the module there.

Obviously using local open can get out of hand, just like any kind of module opening, but a good style guide can go a long way in helping people avoid cryptic code. And it’s not that hard to learn the syntax for it.

austindd avatar Aug 20 '20 20:08 austindd

@austindd it's unclear what direction local open should take at this point. Do you have more concrete example to illustrate your case?

IwanKaramazow avatar Aug 21 '20 05:08 IwanKaramazow

So now ReScript formats a lot of one-liners in our codebase into at least 4-liners, e.g.

let clone = date => Js.Date.(date->getTime->fromFloat);

to

let clone = date => {
  open Js.Date
  date->getTime->fromFloat
}

Something terser would be very appreciated.

EDIT: Local opens are actually a very important feature, because compared to the imports in JS, you only need to have some occasional opens on the top of the file. Now they are all over the place in ReScript, adding more noise to a codebase than some parens would do.

fhammerschmidt avatar Aug 21 '20 07:08 fhammerschmidt

I am running in this while converting our codebase. I realize we have quite a bit of local opens that don't translate very well. Such as:

<View style=Style.(style(~margin=dp(0.), ())) />

That becomes:

<View style={
  open Style
  style(~margin=dp(0.), ())
} />

jfrolich avatar Nov 23 '20 07:11 jfrolich

@jfrolich after infix operators are in, we’ll look into the design and implementation of local open

IwanKaramazow avatar Nov 23 '20 08:11 IwanKaramazow

Ooh that would be awesome!

If I can make one note for when that happens, please don’t automatically optimise single expressions to local open the way refmt does. It’s really annoying 😅

TheSpyder avatar Nov 23 '20 08:11 TheSpyder

I'm confused by the documentation. It says that local opens are possible:

Use open this sparingly, it's convenient, but makes it hard to know where some values come from. You should usually use open in a local scope:

let p = {
 open School
 getProfession(person1)
}

But this thread suggests that it's not possible...?

ersinakinci avatar Dec 06 '20 06:12 ersinakinci

@earksiinni The docs refer to a "local scope" not "local open". When you wrap a block of code in braces, like { /* some code */ }, you create a new scope. The School module is only visible inside the area delimited by the braces. Does this help you a bit?

IwanKaramazow avatar Dec 06 '20 07:12 IwanKaramazow

Ah understood. So local open is like a specialized form of local scope? Basically,

Cn.(...)

is equivalent to

{ open Cn; ... }

?

ersinakinci avatar Dec 06 '20 16:12 ersinakinci

@earksiinni they're different syntax for the same thing. ClassNames.("one" + "two" + "three") is visually just short one-liner vs the more heavy {open ClassNames; "one" + "two" + "three"} (which usually written over multiple lines)

IwanKaramazow avatar Dec 07 '20 06:12 IwanKaramazow

Another place I use this is for importing record fields:

let x = Module.{
  a: 1,
  b: 2
}

In theory I could just annotate the first key, but that looks more messy imo.

let x = {
  Module.a: 1,
  b: 2
}

TheSpyder avatar Feb 17 '21 19:02 TheSpyder

let x = { Module.a: 1, b: 2 }

Annotating keys in record has always looked clumsy to me.

But for this case I guess you can just annotate x.

tsnobip avatar Feb 17 '21 19:02 tsnobip

Maybe? I admit I haven't tried that one, for a long time I ran with warning 40 enabled which requires record fields to always be in scope. I don't anymore though.

TheSpyder avatar Feb 17 '21 22:02 TheSpyder

@tsnobip I think the Reason syntax also allowed this pattern:

let x = Module.{
  a: 1,
  b: 2,
}

This seems more natural and clear.

austindd avatar Feb 18 '21 03:02 austindd

Another place I use this is for importing record fields:

let x = Module.{
  a: 1,
  b: 2
}

This pattern (opening the module right before the record) yielded some shadowing in some cases of ours in the past.

In theory I could just annotate the first key, but that looks more messy imo.

let x = {
  Module.a: 1,
  b: 2
}

This is how we usually do it to avoid shadowing.

The difference being the latter one brings just the record fields into scope, where the former brings the whole module into scope.

Though I have to admit the second one doesn't strike me as beautifull.

woeps avatar Feb 18 '21 09:02 woeps

Ah yes very true, I switch to annotated keys when shadowing is a problem.

TheSpyder avatar Feb 18 '21 09:02 TheSpyder