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

Fantasy Land proposal process for ECMAScript #204

Open
JAForbes opened this issue Nov 22, 2016 · 45 comments
Open

Fantasy Land proposal process for ECMAScript #204

JAForbes opened this issue Nov 22, 2016 · 45 comments

Comments

@JAForbes
Copy link
Member

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
Copy link
Member Author

JAForbes commented Nov 22, 2016

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.

@SimonRichardson
Copy link
Member

SimonRichardson commented Nov 22, 2016

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.

@rpominov
Copy link
Member

rpominov commented Nov 22, 2016

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]))

@gilbert
Copy link

gilbert commented Nov 22, 2016

@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).

@xgrommx
Copy link

xgrommx commented Nov 22, 2016

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

@safareli
Copy link
Member

safareli commented Nov 22, 2016

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

@rpominov
Copy link
Member

rpominov commented Nov 22, 2016

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 promises-aplus/promises-spec#94 .

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

@xgrommx
Copy link

xgrommx commented Nov 22, 2016

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

@JAForbes
Copy link
Member Author

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

@JAForbes
Copy link
Member Author

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
Copy link
Member Author

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

@dead-claudia
Copy link
Contributor

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
Copy link
Contributor

dead-claudia commented Nov 23, 2016

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.

@gilbert
Copy link

gilbert commented Nov 23, 2016

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

@JAForbes
Copy link
Member Author

@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
Copy link
Member Author

Thanks @mindeavor

@JAForbes
Copy link
Member Author

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

@michaelficarra
Copy link
Contributor

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.

@JAForbes
Copy link
Member Author

@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.

@joneshf
Copy link
Member

joneshf commented Nov 24, 2016

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.

@SimonRichardson
Copy link
Member

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

That can be arranged 😛

@JAForbes
Copy link
Member Author

@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?

@rpominov
Copy link
Member

rpominov commented Nov 24, 2016

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.

@dead-claudia
Copy link
Contributor

@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
Copy link
Contributor

@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. 😉

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

@dead-claudia
Copy link
Contributor

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.

@JAForbes
Copy link
Member Author

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

@rpominov
Copy link
Member

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

@JAForbes
Copy link
Member Author

JAForbes commented Dec 1, 2016

Thank you @rpominov

@safareli
Copy link
Member

safareli commented Dec 11, 2016

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

@JAForbes
Copy link
Member Author

@safareli great point.

I was hoping VMs could inline a lot of functions as well if the compositions are static.

@masaeedu
Copy link

@JAForbes Hell yeah for this. Some concepts from Promises/A+ are starting to get baked into the language syntax, duck typing and all, and I feel some design decisions are not being sufficiently contested and hashed out. Ironically, some of the problems (e.g. in https://github.com/tc39/proposal-async-iteration) directly stem from issues where input from the fantasy-land community was dismissed as, well, "fantasy land" suggestions:

  • Confusion over implicit wrapping and unwrapping of promises would not be such a problem if flatMap and map had not been conflated
  • A perfectly synchronized dance between stateful async iterators and their consumers would not be necessary if a separation between describing the computation and initiating it had been maintained

@dead-claudia
Copy link
Contributor

@masaeedu

Some concepts from Promises/A+ are starting to get baked into the language syntax, duck typing and all, and I feel some design decisions are not being sufficiently contested and hashed out.

I agree they weren't all thought through the best (the iterator protocol isn't particularly elegant), but I disagree about the specific argument with Promises/A+ - the thenable interface has been de-facto standardized in Promise libraries as an interop point since around when Angular was even a thing - jQuery adopted Promises/A+ interop, with absorption semantics, when version 2 was first released. So it's been around for quite a while, including the absorption semantics.

And in general in my experience, absorption makes Promise handling way easier, since I only need to care about "am I doing something asynchronous" rather than "did the caller make this asynchronous".

As for standardization points, I feel that there are some things that could be done to improve the lives of functional JS programmers, such as native Option, Try, and Either types (think of Scala's analogues) and some form of conditional pattern matching. You don't need to specifically assist any particular standard, and keep in mind, there's also the Static Land spec and lodash/fp, so they aren't likely to cater to any individual community without first consulting and identifying how it could help others.

@masaeedu
Copy link

@isiahmeadows The recursive flattening that Promises/A+ performs is a useful way of interoperating between the large constellation of promise libraries that have arisen for historical reasons. This doesn't mean then and its recursive flattening are a good abstraction to introduce directly into language constructs.

As a specification for libraries, or even as a class available in the standard library, I'm fine with how Promises behave. However things like await should operate on a more general abstraction than Promises, as is the case in other languages. The fact that a nested promises are reserved as an interoperability concept is baggage associated solely with promises, and should not affect how async functions and async iterators are specified.

@jedwards1211
Copy link

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 agree with this, ECMAScript evolution seems way less corporate and closed off to me than Java, or the way Eran Hammer complained about OAuth 2.0 getting hijacked by enterprise concerns, for instance. The practicality of how ECMAScript has evolved has exceeded my expectations.

There also seems to be an elitist tendency among Haskell users to only consider a certain style of functional programming to be true FP. But anyone who's written a single higher-order function is by definition a functional programmer, even if they barely understand any of the concepts in fantasy land (like me). So anyone who would label TC39 as anti-FP is really accusing them of being anti-algebraic FP, I guess.

@jedwards1211
Copy link

jedwards1211 commented Jan 24, 2018

I'd definitely like to see you guys use Flow or TypeScript syntax rather than Haskell syntax for describing types. That's one thing that smacks of wanting to stick with your current way of thinking and communicate amongst yourselves instead of with the JS community as a whole.

The longer a programmer sticks with any given style of programming, the more they develop a bias toward it and forget how to justify it from first principles and how applicable it is to concrete end goals.

@jedwards1211
Copy link

jedwards1211 commented Jan 24, 2018

@gilbert actually I think the pipeline operator is an example of something many JS devs will see as eminently practical. I certainly want it even though I'm skeptical of Fantasy Land in general.

I think it's a huge misconception that the JS community is biased toward OOP. None of the popular libraries I'm aware of use crazy inheritance hierarchies the way languages like Java or C++ that I came from did.

But whereas some people think the papp syntax is more elegant (though I do think it's a good improvement on bind), I prefer plain old lambdas because I think they're more obvious:

import {flatMap} from 'lodash'
[1, 2]
  |> (_ => _.map(x => x + 1))
  |> (_ => flatMap(_, x => [x, x])) // [2, 2, 3, 3]

Or an API like lodash/fp that does the partial application for you:

import {map, flatMap} from 'lodash/fp'
[1, 2]
  |> map(x => x + 1)
  |> flatMap(x => [x, x])

Sometimes it seems to me that there's a tendency for hardcore functional programmers not to want to express themselves in lambdas. For instance I know some would prefer to write:

[1, 2]
  |> map(add(1))
  |> flatMap(double)

I know in RamdaJS people seem to prefer to write always(42) rather than () => 42. But maybe that's just because some people are sticking to ES5 until lambdas are supported everywhere.

@paldepind
Copy link

@jedwards1211

I'd definitely like to see you guys use Flow or TypeScript syntax rather than Haskell syntax for describing types. That's one thing that smacks of wanting to stick with your current way of thinking and communicate amongst yourselves instead of with the JS community as a whole.

I think that is a very good idea. But, until TypeScript gets higher-kinded types it's unfortunately not powerful enough to express all of the concepts in Fantasy Land. We could invent our own syntax for higher-kinded types on top of TS/Flow and roll with that though.

@joneshf
Copy link
Member

joneshf commented Jan 24, 2018

I think that is a very good idea. But, until TypeScript gets higher-kinded types it's unfortunately not powerful enough to express all of the concepts in Fantasy Land. We could invent our own syntax for higher-kinded types on top of TS/Flow and roll with that though.

Exactly! At first glance it seems like we're spitting in the face of flow/TS. But, that's not the case at all. They just can't encode the concepts we want to express.

If there were a JS type system that was expressive enough, I'm sure we'd gladly switch to it.

@CrossEye
Copy link

@jedwards1211:

I know in RamdaJS people seem to prefer to write always(42) rather than () => 42. But maybe that's just because some people are sticking to ES5 until lambdas are supported everywhere.

I think that's part of it. Another part is simply habit; that's the style promoted by Ramda for anything that smacks of partial application.

And I think part of it is also that words more easily translate to mental concepts than does punctuation. (While it's pretty easy to find where square is in map(n => n * n, vals), it's much easier to find it in map(square, vals).) The trade-off is not being sure which invocations are new function and which are final values. I usually find this trade-off worthwhile.

@JAForbes
Copy link
Member Author

If there were a JS type system that was expressive enough, I'm sure we'd gladly switch to it.

Absolutely! I've tried many times to get Flow and Typescript to work for my projects and I'll keep trying. I'm confident we'll get there.

Also for the record I never said tc39 was anti-fp. I said it's safer if we assume antagonism, as in, let's be diplomatic, polite, and assume no prior knowledge and work within the existing framework that exists which may include certain assumptions about the FP community. It was an attempt at pragmatic advice based on prior experiences that we've had.

And honestly, I'm pretty disappointed with parts of the FP community beyond JS. I think there's a lot to improve. A lot of self defensive snarky jokes that don't explicitly mean harm but send a very clear message that if you aren't in on the joke you are a joke. There's all kind of things we can improve. I wish that stuff wouldn't happen because it makes this process more difficult.

I think the JS FP community is pretty great though. I've certainly felt extremely welcome. And I appreciate all the help and support I've had. I just really want this pointless divide to end and assumptions of bad faith to dissolve. OO vs FP is such a needless waste of energy and from my experience the FP devs in this community have almost no interest in that dichotomy.

I'm optimistic.

@dead-claudia
Copy link
Contributor

dead-claudia commented Jan 25, 2018

@JAForbes I'm actually inclined to agree with you in the FP world sentiment outside JS. In my experience, there's only two exceptions I've found so far:

  • OCaml's community is incredibly utilitarian. You won't find most of the pretentiousness and debate over abstract nonsense there mainly because they themselves purposely avoid it. (They are almost like the C++ community in that they care more about getting things done than debating needless crap.)
    • In particular, OCaml supports classes with full multiple inheritance and mixins through its module system, but it does not support anything like type classes (or any ad-hoc polymorphism, but most uses can be simulated via modules and module functors).
  • Rust is technically an impure functional language (mod the lack of tail call optimization, although that's something they're looking into, along with primitives to support asynchrony), but the community resembles JS more than any other, even up to the "implement everything in JSRust" part, just without the frequent blasting of various language issues/features/proposals. (It helps the language was more cohesively designed from the start.)
    • Note: Rust targets and advertises itself to C/C++ (and similar) people, not Haskell users. This carries into terminology and how it's taught as well. It is one of the few that does not have a GC, though, and it accomplishes that via C++-like lifetimes. This can be somewhat done in GC-bound functional languages using escape analysis and closure inlining, but it can't be done in the general case without lifetime semantics.
  • Swift is about 50% functional in that at the value level, you're generally working procedurally, up to and including using statements rather than expressions, but at the type level, 99.9% of extensibility is done through protocols instead (Objective-C interop notwithstanding), and it has a lot of facilities for things like enums and immutable value types. (In particular, structs are basically named records, and enums are basically sum types. Their switch is also really a pattern matching statement, although it is just that, a statement.)
    • This is mostly a point of comparison. Swift's incredibly procedural tendencies at the value level miss some of the benefit of functional programming. It's like TypeScript's support of narrowing union types in that it helps, but isn't exactly a night-and-day difference.

@dfdgsdfg
Copy link

If there were a JS type system that was expressive enough, I'm sure we'd gladly switch to it.

What about reasonML? It's ML family, not really just type system but familiar with javascript syntax.

https://reasonml.github.io

@dead-claudia
Copy link
Contributor

@dfdgsdfg I'll note that the ML family does not have type classes - it works more like Static Land than Fantasy Land.

@futpib
Copy link

futpib commented Mar 3, 2018

Here is something I'd like to see in JS and would like to get feedback on. This is basically a do-notation like syntax and a generalization of existing async/await syntax (actually, I think this is how it should have been implemented in the first place).

// Number -> Maybe String
const findFullName = chain (id_) => {
  const { firstName, lastName } = from S.find(({ id }) => id === id_, users);
  return `${firstName} ${lastName}`;
};

findFullName(2);
// -> Just('Bob Marley')

findFullName(3);
// -> Nothing

The above function declaration would be equivalent to calling .chain.

More examples: https://github.com/futpib/es-monadic-chain

What do you think?

EDIT: related #282

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