fantasy-land icon indicating copy to clipboard operation
fantasy-land copied to clipboard

Fantasy Land proposal process for ECMAScript

Open JAForbes opened this issue 8 years ago • 45 comments

I'm proposing the fantasy-land community contributes more actively in the ECMAScript language design process. There's been some discussion on gitter, and we have at least 1 proposal idea.

I think it would be good to get a sense of what the process is, so if anyone has any suggestions please say so!

@davidchambers suggested @michaelficarra as a potential ally. I'd like to get @mindeavor's input as well with his experience proposing https://github.com/mindeavor/es-pipeline-operator

A candidate for our first proposal is https://github.com/fantasyland/function-prototype-map suggested by @puffnfresh

JAForbes avatar Nov 22 '16 11:11 JAForbes

One administrative thing worth sorting out. Where should proposals be discussed? Should we have a separate repo on fantasy-land to namespace these discussions?

Interested in your thoughts on that @rpominov as you seemed hesitant on gitter.

JAForbes avatar Nov 22 '16 12:11 JAForbes

Where should proposals be discussed? Should we have a separate repo on fantasy-land to namespace these discussions?

Yes please, esp. with long running discussions, it can get confusing.

SimonRichardson avatar Nov 22 '16 12:11 SimonRichardson

Maybe we should crete some placeholder repo like https://github.com/jsforum/jsforum for all general discussions that are not related to any particular repository? Something like fantasyland/forum.


But meanwhile and before I forgot I'd like to mention here another language feature, that potentially could make into a good proposal.

A special syntax for partial application of a function:

foo#(a, b) // desugars roughly to foo.bind(null, a, b)

Using this and pipe operator we could write code like this:

[1, 2]
 |> List.map#(x => x + 1)
 |> List.chain#(x => [x, x]) // [2, 2, 3, 3]

Which a smart enough compiler could desugar to:

List.chain(x => [x, x], List.map(x => x + 1, [1, 2]))

rpominov avatar Nov 22 '16 15:11 rpominov

@rpominov Adding # as syntax will be a rough and uphill battle. It's hard enough convincing to add papp to Function.prototype.

[1, 2]
  |> List.map.papp(x => x + 1)
  |> List.chain.papp(x => [x, x]) // [2, 2, 3, 3]

@JAForbes Re experience: You need a TC39 champion to be interested in your proposal to move it forward at all. Take note that they're all heavily biased towards OOP, so FP features, even a simple one as the pipeline operator, will likely be seen as "bloat" or "unnecessary" (unlike classes and their bundle of supporting features, apparently).

gilbert avatar Nov 22 '16 17:11 gilbert

@rpominov u can write some plugin for babel. For example https://www.sitepoint.com/understanding-asts-building-babel-plugin/

xgrommx avatar Nov 22 '16 19:11 xgrommx

btw we can do so something like this using es6 template literals:

const _  = someCoolName({
  '|>' : (L,R) => R(L),
  '>>=': (L,R) => L.chain(R), 
  '<*>': (L, R) => R.ap(L),  
  '<$>': (L, R) => R.map(L), 
})

const res1 = _`
  ${[10]}
    |>  ${a => a.concat([11])}
    >>= ${a => [a + 1]}
    >>= (
      |> ${a => a * 2} 
      |> ${a => [a, a - 1]}
    )
` // [22, 21, 24, 23]

const res2 = _`
  ${[
    a => b => a + b,
    a => b => a - b,
  ]}
    <*> ${[1,2]}
    <*> ${[3,4]}
` // [4, 5, 5, 6, -2, -3, -1, -2]


const res2 = _`
  ${[
    a => b => a + b,
    a => b => a - b,
  ]}
    <*> ${[1,2]}
    <*> ${[3,4]}
` // [4, 5, 5, 6, -2, -3, -1, -2]

const res3 = _`
  ${getCurrentLocation /*:: IO Location*/}
    >>= ${getHashFromLocation /*:: Location -> IO hash*/}
    >>= ${hash => fetch /*:: [URL] -> IO [Responce]*/([
      `/api/users/${userIDFromHash(hash)}`,
      `/api/posts/${postIDFromHash(hash)}`,
    ])}
    >>= ${([uRes, pRes]) => _`
      ${makePage /*:: [User] -> [Post] -> Page */}
        <$> ${userFromResponce(uRes)}
        <*> ${pageFromResponce(pRes)}
    `}
` // :: IO Page

safareli avatar Nov 22 '16 19:11 safareli

Adding # as syntax will be a rough and uphill battle. It's hard enough convincing to add papp to Function.prototype.

Sad to hear that. Function.prototype.papp proposal looks great, btw!

Take note that they're all heavily biased towards OOP, so FP features, even a simple one

This is also very sad, although I've suspected this. Seems like overall attitude haven't changed that much since https://github.com/promises-aplus/promises-spec/issues/94 .

Very curious about @michaelficarra's perspective, hopefully it's not so bad.

rpominov avatar Nov 22 '16 20:11 rpominov

@rpominov @safareli As you remember bilby http://bilby.brianmckenna.org/#do-operator-overloading

xgrommx avatar Nov 22 '16 22:11 xgrommx

@mindeavor thanks for the info! That's very helpful.

JAForbes avatar Nov 23 '16 02:11 JAForbes

I'm going to say a few blunt harsh truths I think we need to internalise if we are going to have any success.

We are walking into a world where OO is king, Haskell is a symbol for the programming elite, and profit matters.

We need to acknowledge some hard truths, Javascript's history is political, born out of a corporate war, designed in 10 days as a result. Large corporate entities vie for control of the web and they all have their own particular slant. "Anyone can be a part of TC39" as long as your company is willing to pay a fee.

Look at the list of members: http://tc39wiki.calculist.org/about/people/ the overwhelming voice in the committee are large corporate entities that want to make their particular vision of the web happen.

Its not a democracy, its an oligarchy, we need to admit that up front if we are going to come up with a strategy that makes any difference at all.

We need to figure out up front, what our goals are, what the obstacles are, and how do we communicate those goals. Our changes need to be in the committees interests, in other words they need to save corporations money, or make corporations money.

As for ESDiscuss, I'm not sure what role that mailing list plays, but from the threads I've read, getting a receptive response relies on the readership to understand what the feature is and why it is beneficial. I think we'll hit a wall if we get too abstract here, if we get bogged down in theory or elegance. We need to talk about why a given feature will lead to less bugs, or less code, or simplify implementations of well known libraries. We need to relate our proposals back to the mainstream e.g. how would this feature be used in a React app? And we need to assume absolutely 0 knowledge or appreciation of functional programming - in fact it is probably safer to presume antagonism.

Its easy to get defensive, but that won't help us change the language. We need to be calm and patient and ultimately we will need to suffer ignorance graciously. We need to make our case on their terms because at this point we need them and they don't need us.

We have to keep in mind, we are not trying to convince tc39 that FP is the right direction, we are trying to save millions of JS developers time by giving them access to a better set of tools.

We need to lean on the goodwill of the subset of FP that OO people like (Rx/Observables, linq, Elm, F# and React, FlowType/TS). We need to tread lightly referencing Haskell, ML, Categories, Monads etc. We can't start there. We have to be realistic of the terrain. Let's try and get one proposal into the language to prove the value of our counsel, to dispel the myth that FP has no real benefits, that it is all "theoretical, ivory tower, academic nonsense". I know that is the prevailing perspective, we've all seen it I'm sure.

I know how hard that it is to hear, but we know better. It is on our shoulders to communicate the value downstream.

I think we need to stop talking about libraries/transpilers/hacks to make JS better. We are a community who deserves a voice in the process but to date our default reaction is to write a new language, or create a library etc.

Those reactions are all valid and worthy and should be continued. But we need to also direct our collective force at the web platform itself. I think it will be hard, I think we'll need to form a careful strategy, but so many JS users will benefit if our counsel is heard.

Evan Czaplicki's "Let's be Mainstream" talk comes to mind.

I also say this with absolute deference to the knowledge of this community. I know I have a lot to learn about FP, that I am in no way an expert in this field. But I do feel I am a bridge between this world and the other, I can see their connotations and I can empathise with them.

So let's come up with a list of proposals, discuss the pro's / cons of each proposal and different strategies. Maybe we want to put all our weight in one proposal, or maybe its better to hit them with many proposals simultaneously? Let's study previous successes and previous failures and learn from them. But let's not embark on a war of principles, let's not attempt to convert the JS community to "see the light" - that will never work, we need to identify our goals and guide our interactions with the committee to achieve those goals, nothing more.

JAForbes avatar Nov 23 '16 03:11 JAForbes

I've got some input from @isiahmeadows re: es-discuss. You can subscribe to the mailing list here:

https://mail.mozilla.org/listinfo/es-discuss

And you can make proposals by posting an email to [email protected]. The goal is to convince a member of tc39 to become a "champion" of your feature, which then moves your proposal to stage 0.

I don't think we should make any proposals yet, but that's just my view on it. But I'm going to subscribe and get a sense of the climate. Also turns out there is already a proposal for a composition operator in the mailing list: https://esdiscuss.org/topic/function-composition-syntax which might be of interest.

Here are some notes on contributing to ECMAScript

https://github.com/tc39/ecma262/blob/master/CONTRIBUTING.md

JAForbes avatar Nov 23 '16 12:11 JAForbes

Note: at the bottom of that email thread about function composition, here's my gist strawman for it. I'd definitely take suggestions on how to improve upon it. If we come up with something worth adding, that would still work within the confines of the language, this would be wonderful.

dead-claudia avatar Nov 23 '16 12:11 dead-claudia

Here's what I'm thinking:

  1. Standardize a basic set of protocols based on a subset of the basic algebras here. Let's not add the full package yet, but we can maybe round it out later. (I'm not too attached to the protocol names - they can change)

    • Setoid (equals)
    • Semigroup (concat)
    • Monoid (Semigroup + empty)
    • Functor (map)
    • Apply (Functor + ap)
    • Applicative (Apply + constructor.of)
    • Chain (Apply + chain)
    • Monad (Applicative + Chain)
    • Bifunctor (Functor + bimap)

    (Why include so little? Consider Promises, iterables, etc.: it's much easier to flesh out known, commonly implemented semantics. Most Promise implementations were ES6-compatible from the start, for example.)

  2. Propose that several existing types implement these protocols:

    • All primitive types (including boxed types) except null/undefined implement Setoid
    • Objects implement nothing
    • Functions implement Monad
    • Symbols implement Monad and Setoid
    • Arrays, TypedArrays, Maps, and Sets implement Monad, Monoid, and Setoid
    • WeakMaps and WeakSets implement nothing
    • %IteratorPrototype% implements Functor
    • Promises implement Monad and Bifunctor (return on right)
    • Proposal-wise: %AsyncIteratorPrototype% implements Functor, etc.

    Note that equals on collections works recursively, and that iterator.map(f)'s return value's prototype is %IteratorPrototype%.

  3. Push for the bind operator proposal (or some other pipelining operator) to gain better traction and increased priority.

  4. Propose a few new classes:

    • Option or Maybe, implements Thenable (with coercion, undefined error), Monad and Setoid (either one works)
    • right-biased Either, implements Thenable (flipped, with coercion to right), Monad, Bifunctor, and Setoid
    • return-biased Try, implements Thenable (with coercion), Monad, Bifunctor, and Setoid (for safer exception handling)

We should allow methods to accept arguments beyond what each protocol requires in the proposal itself, since some types with existing correct methods (like Array with Array.of, array.concat, etc.) accept more parameters than necessary.

As for polyfills for steps 2 and 4, I suspect it'd be extraordinarily straightforward to do. Promises will require a boilerplate object to prevent coercion with map/etc., but that's about the end of it. Note that the invariants of callbacks (e.g. matching prototypes for map) are checked, though, and failure is an immediate synchronous TypeError, or synchronous rejection in the case of Promises.

Note that those that implement Bifunctor also implement Thenable, with the arguments in reverse order and with coercion (like what Promises do today).


Oh, and with these, JS might start feeling a little like a lighter, more dynamic Scala.

dead-claudia avatar Nov 23 '16 13:11 dead-claudia

For the record, here's the discussion on the pipeline op: https://esdiscuss.org/topic/the-pipeline-operator-making-multiple-function-calls-look-great

gilbert avatar Nov 23 '16 20:11 gilbert

@isiahmeadows that is a lot more ambitious than I would have expected we could be. But you do have a lot more experience in these matters.

Few questions:

  1. why does Object implement nothing, is that a performance concern?
  2. Do you think pushing for bind would help our cause in the future, or hinder it? I don't think many here are in favour of bind syntax (correct me if I'm wrong). Would getting bind into the language create the impression they've already catered to the FP community so pushing for compose/map/pipe would be harder? Or do you think just getting one proposal through the door would help future proposals?
  3. Why would synchronous types like Option implement thenable? That seems counter to the principle of Promises always be async, also I think this community would prefer map.
  4. Do you think we'd gain more traction if chain was renamed to flatMap when proposing monads?
  5. Do you think we should use terms like Monad/Setoid in esdiscuss? I'm worried it will create the impression that this feature is not useful for most people. But maybe its better to bite the bullet use the correct terms and just be gracious when introducing concepts and provide lots of examples? I hope for the latter I'm just concerned A+ will happen again.

JAForbes avatar Nov 24 '16 02:11 JAForbes

Thanks @mindeavor

JAForbes avatar Nov 24 '16 03:11 JAForbes

@SimonRichardson also I can't act on your request I'm not a member.

JAForbes avatar Nov 24 '16 03:11 JAForbes

Hey, everyone. Thanks for the mention. I think I can help out a bit here.

I think there have been some mischaracterisations of TC39 as a whole. TC39 has many members, and the representatives that participate have very diverse backgrounds and goals. It would be wrong to try to label the entire group as "anti-FP" or really subscribing to any sort of unified philosophy.

I also don't think that proposals of individual prototype methods are the best way for this community to get started contributing. And I know that the committee would never pull in the entire Fantasy Land protocol wholesale. Every proposal that the committee works on is motivated by real world usage, often already proven out by successful libraries/frameworks or implemented in compile-to-JS languages. So I think two strategies should be taken: build out as much of what you want to see in libraries/frameworks/languages (done here already; good job!) and propose features that will allow you to do more of this prototyping yourselves without involvement from the language authors. Let me give an example.

A few months ago, I prototyped the definition of algebraic interfaces if we were given a kind of "mixin" or "interface implementation" syntax on top of classes. I defined a couple of "type classes" which define their protocol using static symbols (to permanently avoid name collision), then use a new with syntax to extend or implement that interface. Now if I fudged the new syntax bits into some function calls (or just used sweet.js macros), I could get this working today as-is. And that would show that this feature has a real use case. And then, once it is in the language, that feature could be used to create an even more easily implementable and even better "standard protocol" like Fantasy Land. Once it gains enough traction, it would be a no-brainer for the committee to include something like it in the language.

So, at least in my opinion, this won't all happen at once. We need to take a bunch of small steps in a direction that will inevitably lead to the place we actually want to be. Push for an interface-like feature or macros to help in proving out prototypes. Symbols were a big win and we didn't even know it!

I love this community and this effort (I have implemented it in some of my own projects), so feel free to lean on me for advice regarding the TC39 process or to champion a proposal.

michaelficarra avatar Nov 24 '16 04:11 michaelficarra

@michaelficarra Thank you for the advice. There are a lot of helpful insights there that I think we can act upon. I apologise for mis-characterising the committee themselves. My previous comment is really addressing systems and structures. I'm not assuming the members themselves have some ill intentions. That's some advice we can act upon productively.

This is probably a discussion for another time and another place - but I do think a lot of the criticisms and frustrations that are directed at the process are valid, and it would be disingenuous for me to pretend otherwise. Things have definitely improved, discussions are in the open, and are informed by the community. But there is a corporate story there, and whether or not it is intentional (I'm sure it isn't) there is an identifiable power imbalance. But I have no malice toward anyone, its all just people at the end of the day and its a hard thankless job.

It would be great if there was a process for iterating on the process and not just the language.

It seems there are already a lot of functional proposals in the mix, maybe a productive avenue for us is to research the existing proposals and see if they are something our community can get behind, and if not we can debate possible amendments to those proposals and add those amendments to the esdiscuss thread.

Maybe that is a good first step.

JAForbes avatar Nov 24 '16 05:11 JAForbes

I'm surprised Traversable wasn't in that list.

  • You can get Functor from it, plus all things you can derive from Functor.
  • You can get Foldable from it, plus all things you can derive from Foldable.
  • There are a ton of data types that can implement it.
  • There are additional things it provides on its own, like scan.
  • It's a small step away from the van Laarhoven lens hierarchy.
  • You could technically derive it automatically instead of by hand.

joneshf avatar Nov 24 '16 05:11 joneshf

@SimonRichardson also I can't act on your request I'm not a member.

That can be arranged 😛

SimonRichardson avatar Nov 24 '16 15:11 SimonRichardson

@SimonRichardson @rpominov So which way should we go? A repo for tc39 discussions, or a general forum repo for discussions not directly related to any particular project?

JAForbes avatar Nov 24 '16 22:11 JAForbes

One point for a general repo is discoverability. Say we have an issue discussing creation of a new library there, someone comes to that issue from a link and finds discussions about ES proposals in other issues in same repo.

But I guess either way is good anyway.

rpominov avatar Nov 24 '16 22:11 rpominov

@JAForbes Sorry for the late response, but in reply to this comment:

why does Object implement nothing, is that a performance concern?

Practical concern. You could argue for equals being this === other by default, but it wouldn't make sense for it to implement the rest, like, say, map or concat. Otherwise, they'd bleed into other types like functions (which shouldn't implement concat) or strings (which shouldn't implement map).

Do you think pushing for bind would help our cause in the future, or hinder it? I don't think many here are in favour of bind syntax (correct me if I'm wrong). Would getting bind into the language create the impression they've already catered to the FP community so pushing for compose/map/pipe would be harder? Or do you think just getting one proposal through the door would help future proposals?

Why would synchronous types like Option implement thenable? That seems counter to the principle of Promises always be async, also I think this community would prefer map.

This was initially for compatibility with Promises, but it'd be mostly useful for those who want auto-absorption semantics (like what exist with Promises). Admittedly, when I write Option types, I myself include absorption just to simplify the implementation and avoid boilerplate.

Do you think we'd gain more traction if chain was renamed to flatMap when proposing monads?

Yes, and IMHO that would be preferable. chain is a little odd compared to, say, Java's Stream.flatMap or Rx.js's Observable.prototype.flatMap.

Do you think we should use terms like Monad/Setoid in esdiscuss? I'm worried it will create the impression that this feature is not useful for most people. But maybe its better to bite the bullet use the correct terms and just be gracious when introducing concepts and provide lots of examples? I hope for the latter I'm just concerned A+ will happen again.

It might be better to use them (e.g. the term "monad" is starting to become better understood by those less versed in type theory), but with a thorough explanation of how each distinct concept works, why they are distinct concepts (e.g. tuples are applys that aren't applicatives, strings are monoids that aren't monads, among others), and how they relate to OO idioms. I'll note that monads will be easy (they're already using them), but applies that aren't applicatives may be a little harder.

I do suspect names might change as time goes on, mainly because the OO and FP communities don't use the same terminology. What we call monads, they call object wrappers. What we call comonads, they call builders and factories. What we call most monadic monoids, they call collections. Oh, and don't forget the similaries between SQL and FP. It's pretty obvious when you consider the F# version here as a direct translation of the SQL, just using immutable sequences instead:

-- SQL
SELECT Foo.One, Bar.Two FROM Foo
    INNER JOIN Bar ON Foo.Id = Bar.FooId
    WHERE Foo.Total < 100
    SORT BY Bar.Name ASC;
// F# equivalent
fooList
|> Seq.collect (fun foo ->
    barList
    |> Seq.filter (fun bar -> bar.FooId = foo.Id)
    |> Seq.map (fun bar -> (foo, bar))
|> Seq.filter (fun foo -> foo.Total < 100)
|> Seq.sortBy (fun (_, bar) -> bar.Name)
|> Seq.map (fun (foo, bar) -> (foo.One, bar.Two))

dead-claudia avatar Nov 28 '16 09:11 dead-claudia

@JAForbes

[...] that is a lot more ambitious than I would have expected we could be.

You can't exactly expect to get anywhere without at least a hint of idealism. :wink:

And this is merely adding a bunch of easy-to-polyfill methods initially.

dead-claudia avatar Nov 28 '16 09:11 dead-claudia

I was just thinking that, in the end, this may be better done with decorators and similar instead. Things like this:

@Functor
class Foo {
    map(f) {}
}

@Monad
class Bar {
    chain(f) {}
    static of(x) {}
}

// etc.

Decorators could fill in all the derivations as well. So, instead of having to implement everything, all you would actually need to implement for a basic List or Promise would be this:

@Monad
@Monoid
@Traversable
class List {
    static of() {}
    static empty() {}
    equals(other) {}
    concat(other) {}
    chain(other) {}
    traverse(f, T) {}

    // Derived:
    // map(f) {}
    // ap(f) {}
    // reduce(f, acc) {}
}

Furthermore, chainRec could be easily derived from any chain with a general function like this, mod the stack space requirement:

const result = done => value => ({done, value})
function chainRec(f, acc) {
    const {done, value} = f(result(false), result(true), acc)
    return done ? value : value.chain(v => chainRec(f, v))
}

It won't be trivial to both avoid blowing the stack and generalizing for both sync and async chains, but pull-stream's drain implementation would be highly informative in this area (they also have to deal with maybe-sync, maybe-async). IMHO, that belongs in Ramda/etc., not here. But I'll stop before this gets too off-topic.

dead-claudia avatar Nov 28 '16 10:11 dead-claudia

@rpominov @SimonRichardson could either of you create the discussion repo? I don't have sufficient permissions.

JAForbes avatar Nov 28 '16 22:11 JAForbes

Here you go https://github.com/fantasyland/ECMAScript-proposals

rpominov avatar Nov 29 '16 07:11 rpominov

Thank you @rpominov

JAForbes avatar Dec 01 '16 01:12 JAForbes

btw, If we had a function composition operator in a language, VMs could optimize stack usage, when running composition of many functions without need to do it manually this way

safareli avatar Dec 11 '16 10:12 safareli