jamiebuilds / ghost-lang

:ghost: A friendly little language for you and me.
302 stars 10 forks source link

No/Limited Destructuring #39

Open jamiebuilds opened 5 years ago

jamiebuilds commented 5 years ago

Destructuring has been part of my design for Ghost for a really long time. But I've come to avoid destructuring in my JavaScript code, and I'm actually starting to think it's a bad language feature.

For the record, I think destructuring is fine in the simple case:

let { name, followerCount } = user
let str = "{name} ({followerCount}"

Although, I kinda think it's better to just write it out like:

let str = "{user.name} ({user.followerCount}"

But people take it to an uncomfortable extreme:

let {
  propertyOne = false,
  propertyTwo = null,
  propertyThree as someThing,
  propertyFour: {
    nestedProperty: {
      anotherNestedPropertyA as a = defaultValue,
      anotherNestedPropertyB as b
    } = {}
  } = {}
} = props

I've seen destructuring code 4-5x longer than this too.

It's not great on it's own, but it gets much much worse when you have references to destructured values all over the place and have no idea where any of them came from.

let {
  propertyOne = false,
  propertyTwo = null,
  propertyThree as someThing,
  propertyFour: {
    nestedProperty: {
      anotherNestedPropertyA as a = defaultValue,
      anotherNestedPropertyB as b
    } = {}
  } = {}
} = props

# ...100 lines later...

doSomething(a, b) # ... where the heck did `a` and `b` come from?

Sure it's easy enough to "click to definition", but it's not a great experience to jump back and forth in the same file over and over many times.

It's much better to read things like:

fn(entity.field, otherEntity.anotherField) 

I think this comes down to "thinking in entities", which is an aspect of more object-oriented-style programming that I actually like a lot. I think it's good when people define their data structures clearly.

I think that destructuring encourages people to throw out their data structures and merge them into giant records that contain every little piece of data you would want.

# Bad: What is any of this data supposed to be?
let f = fn ({ id, name, setting1, setting2 }) {
  # ...
}

# Good: More clear about where this data should be coming from.
let f = fn (user, userSettings) {
  # ...
}

As a result of this, I would like to either remove destructuring from Ghost or severely limit it in the syntax.

I'm 90% sure I don't want to allow destructuring in function parameter lists (It's kind of weird that you'd want to considering Ghost has named parameters):

let f = fn ({ value, otherValue }) {
  # ...
}

I'm 90% sure I don't want to allow nested destructuring:

let { nested: { value } } = obj

I think I do want to keep destructuring for imports and for variable initializers:

import Math as { sin, cos, tan }
let { one, two, three } = rec

I'm not sure if I would want to keep features like defaults, spread, or renaming. Allowing one seems to necessitate the others, and spread seems valuable enough to keep. Part of me wants to keep them out of Ghost so that destructuring has limited usefulness, but I don't want to make them totally unusable.

let { value = 42 } = obj
let { value, ...rest } = obj
let { value as other } = obj
SCKelemen commented 5 years ago

I think out of these changes, the only thing I would miss is spread. I think spread is very useful, and it's helpful for builder patterns, cloning, etc. Nested destructuring is a beast. I think I would like to keep simple destructuring but nested and renaming have never been that useful to me, and I would suspect the burden of supporting those features combined with the possibility of foot-gunning yourself in the future, would be sufficient reason to avoid it.