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

Privacy #35

Open
jamiebuilds opened this issue Apr 15, 2019 · 4 comments
Open

Privacy #35

jamiebuilds opened this issue Apr 15, 2019 · 4 comments

Comments

@jamiebuilds
Copy link
Owner

jamiebuilds commented Apr 15, 2019

I've been thinking a lot about privacy as it pertains to functional programming.

Generally I prefer the idea of privacy through inaccessibility. AKA, if you don't want someone to have access to something, just don't expose it anywhere that could be accessible.

For example, (ignoring privacy details for a second) in JavaScript we could write a User class like this:

export class User {
  constructor(id) {
    this.id = id
  }

  createApiRequest(method, endpoint, body) {
    return fetch(`/api/users/${this.id}/${endpoint}`, { method, body })
      .then(res => res.json())
  }

  fetchPosts() {
    return this.createApiRequest("GET", "posts", {})
  }
}

Syntactically this implies that you could call user.createApiRequest() and it's only by the language adding a new feature for privacy (In JavaScript: #private members) that it becomes clear you aren't meant to access something.

However, you could write the same code using just functions and exports:

function createApiRequest(userId, method, endpoint, body) {
  return fetch(`/api/users/${userId}/${endpoint}`, { method, body })
    .then(res => res.json())
}

export function fetchPosts(userId) {
  return createApiRequest(userId, "GET", "posts", {})
}

Here you get privacy just by never syntactically exposing (or rather export-ing) anything.

However, there are still places where not exposing something to achieve privacy is more effort. For example, take this recursive sum() function:

let sum = fn (numbers: Array<Number>, total = 0) {
  if (numbers.length) {
    sum(numbers[1...], total + numbers[0])
  } else {
    total
  }
}

Here, total is exposed by being part of the function signature, but for the purposes of this example we don't want that to be an exposed part of the API.

So instead we have to wrap the sum function with another function:

let sum_ = fn (numbers, total = 0) {
  if (numbers.length) {
    sum_(numbers[1...], total + numbers[0])
  } else {
    total
  }
}

let sum = fn (numbers) {
  sum_(numbers)
}

This works, but is kinda gross, and I'm afraid people wouldn't reach for this right away. Possibly instead writing their code in an iterative fashion so they can have internal state.

One option I came up with was adding a feature to Ghost for private function arguments:

let sum = fn (numbers, private total = 0) {
  if (numbers.length) {
    sum(numbers[1...], total + numbers[0])
  } else {
    total
  }
}

In this case, you are allowed to use private arguments when calling the function inside of itself, but not from any other function/context.

This gives you two different function signatures, one from within the function and another from outside. Which is no different from how classes are in most languages, but is (I think) unusual for Ghost.

I'll continue thinking about this, but for now I'm defaulting to not adding a language feature unless I think it dramatically improves things.

@SCKelemen
Copy link

How would this be any different than overloading a function and exposing only one of the overloads? The exposed overload could just call the internal overload with the default parameter.

@jamiebuilds
Copy link
Owner Author

It's not any different, but adding overloading is a whole other featureset that I don't feel great about

@SCKelemen
Copy link

Go takes your exporting suggestion one step further, and instead of introduce an export keyword, it uses the first letter of they Identifier to indicate accessibility. PascalCase is enforced for exported identifiers and camelCase is enforced for unexported identifiers. Have you considered something like this?

I'm not advocating either way, just bringing up something to be considered.

@jamiebuilds
Copy link
Owner Author

I've been on the fence about identifier naming having special meaning. I like stuff like _unused vs used because that has no real effect. But to change semantic meaning based on an identifier name seems like too much.

Vaguely:

Yes: Semantics enforcing identifier name
No: Identifier name causing semantics

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