DEV Community

Cover image for The Case for TS+

The Case for TS+

Michael Arnaldi on April 10, 2022

Note from 2023: TS+ is an experimental fork of TS, don't use it in prod. Hi! So, what's this post gonna be about? Well, with the Effect com...
Collapse
 
patroza profile image
Patrick Roza

First Effect, now TS+. There was no better day to be a functional programmer than today :)

After spending a lot of time with pipe, no useful language supported pipe in sight, and lots of imports, hard to discover modules and apis, ts+ is such a breath of fresh air, hitting all the right notes at the right time!

Already adopted fluent, static and call extensions, and globals. Can’t wait to go further with operators, lazy args, rules and beyond.
And soon, the stdlib with next gen Effect!

 
mikearnaldi profile image
Michael Arnaldi

Lodash/Underscore are not Fluent APIs they are just modules of functions that is ok, there is a usability compromise though take for example an array "a" to map "a" you do "a.map(f)" doing "map(a, f)" isn't as nice, nor doing "pipe(a, map(f))", jQuery has a fluent api but that sort of comes from a different era and nobody is tree shaking jq. Fluent APIs cannot be tree-shaken (at all), class methods cannot be removed nor renamed, libraries like fp-ts or rx-js have moved away from those API because they aren't shakable and they aren't extensible (i.e. adding a method to a class coming from a library)

Collapse
 
mikearnaldi profile image
Michael Arnaldi

This is a fork of the compiler it is not adding any further compilation step, and it is fully compatible with normal TypeScript (it really doesn't want to be against TS at all :))

Collapse
 
patroza profile image
Patrick Roza

I guess you could say it's a ttypescript or ts-patch with already great built-in patches.

Collapse
 
ninofiliu profile image
Nino Filiu

Maybe I didn't get something bug I feel like the base issues could have been solved in an extremely easier fashion by not using class-based OOP

All methods have to be inside the class (or inside a common abstract class for ADTs)

No, just use functions

Once a class is defined adding methods to it from the outside requires module augmentation and unsafe mutation of object prototypes

No, just use functions

Worst of all, none of it can be optimized from a tree shaking perspective, so you'll end up with a huge bundle size that becomes prohibitive at a certain point

No, just put the functions in different files

The current "solution" that the FP community has adopted is the use of pipeable APIs.

No, some people just use functions

Collapse
 
mikearnaldi profile image
Michael Arnaldi

"No, just use functions" => this is in the context of wanting a fluent api, if you want a fluent API plain functions are of no help.

"No, just put the functions in different files" => by the way this is 100% irrelevant for tree shaking, you can put all the functions in the same file and the result is the same (actually even better in size terms because you have less modules).

"No, some people just use functions" => yes people like me use just functions, but they also know what a function actually is.

Collapse
 
mikearnaldi profile image
Michael Arnaldi

both RxJS and fp-ts uses pipeable functions. pipeable functions are as you say "just functions", the same way extension methods are "just functions"

Collapse
 
tamusjroyce profile image
tamusjroyce

“reactive” libraries like rxjs are event systems. Because they have a disconnect between the data being sent, and overhead for subscribing/unsubscribing, I wouldn’t consider them anything related to fluent design patterns.

I personally prefer pure functions, and changing state at the very last pop of the call stack (firsr method that calls pure functions is responsible for setting state, no where else can set state). Similar to fluent, but without the overhead of returning a new object. And easier to debug what is going on (fluent can turn into a finite state machine).

Thread Thread
 
mikearnaldi profile image
Michael Arnaldi • Edited

Fluent here refers to a style of API not the design pattern and has nothing to do with mutating state, the IO example above is 100% pure and functional in every possible interpretation.

Collapse
 
hamidb80 profile image
Hamid Bluri

Use Nim programming language and you don't care whether it's a method or function

You write whatever Style you want

Thread Thread
 
mikearnaldi profile image
Michael Arnaldi

Big part of the project is to create something both really nice and with great interop with the wider ts ecosystem it's not only about style :)

Collapse
 
ninofiliu profile image
Nino Filiu

So when programming a user needs to know exactly which module to import, exactly which functions are constructors of the datatype and which are methods for the datatype, and exactly how to use them.

That I really don't understand. I'm not familiar with IO or Effect but for example in lodash when I wanna use cloneDeep I'll just do import cloneDeep from 'lodash/cloneDeep';, yeah I have to know which module to import but it's not giving me a headache. Also there's no constructors and no additional methods, and the use is evident. So I don't see any case where a similar lib would cause these issues

Collapse
 
mikearnaldi profile image
Michael Arnaldi

Sure you can use directly lodash and direct imports, the problem becomes more apparent when using libraries that cover a bit more surface compared to lodash (like Effect, like fp-ts, etc) and in many codebases that translates to a large set of namespace imports import * as F, when using F you have everything inside and discoverability is problematic. For example only the Effect module in effect-ts provides 1000+ functions inside (they are all as you say "just functions") but you have to know what the "just functions" do, i.e. is the function constructing an effect or is a function combining? i.e. map combines, succeed creates

Collapse
 
mikearnaldi profile image
Michael Arnaldi

What can you do of the above with vanilla JS? Derive runtime codecs? have tree-shakable fluent APIs?

Collapse
 
joshuaamaju profile image
Joshua Amaju • Edited

Was really confused on looking through Effect and its community libraries and seeing fluent API. I was like "what's going on here", and I kept seeing tsplus in the code and tsconfig. I was about checking npm for tsplus then found your post on the discord channel.

 
mikearnaldi profile image
Michael Arnaldi

Yeah, incorporating those functions (map/flatten/etc) you end up with all, for effect that can be huge there are modules with 1000+ functions

Thread Thread
 
patroza profile image
Patrick Roza • Edited

@jfbrennan importing individually gains tree shaking but would mean losing chaining/fluent and therefore discoverability and usage context.

reduce(flatten(map(lyrics, line => line.words.split(' '))), (counts, word) => {
    counts[word] = (counts[word] || 0) + 1;
    return counts;
  }, {})
Enter fullscreen mode Exit fullscreen mode

or piped

pipe(
  lyrics,
  map(line => line.words.split(' ')),
  flatten,
  reduce((counts, word) => {
    counts[word] = (counts[word] || 0) + 1;
    return counts;
  }, {})
)
Enter fullscreen mode Exit fullscreen mode

With ts+ you keep chaining/fluent, discoverability and usage context, while it gets compiled down to individual imports for tree shakability and optimisations. win-win.
The bigger the library (or the more libraries), with the more type classes/modules, the more the win is.

Collapse
 
danielo515 profile image
Daniel Rodríguez Rivero

Are the functions showed here part of the compiler itself ? So I can just use them without any extra installation?
It looks awesome, good work, and kudos for going one step further of what most libraries could do and fork the compiler.

Collapse
 
mikearnaldi profile image
Michael Arnaldi

The functions/values as in their material implementation is in library code, in this case what's shown is part of @tsplus/stdlib that you can use with or without the compiler fork and should be installed with classic npm/yarn, when using the compiler fork the same functions defined in stdlib (or any other packages like @effect/core, or your local code) are used as concrete implementations but additional syntax is generated and made available to use, the additional syntax includes fluent methods, operators, etc. Also when using the compiler fork your functions can specify derivation rules and values can be defined as implicit instances that are used when a call to Derive() is compiled in order to generate things like Encoder/Decoder/Guard/Equals and any custom typeclass that you may want.

Collapse
 
milottit profile image
milottit

Looks promising! But why the use of comments? It is not really friendly to use. Why did you end up with this solution over new keywords or typescript decorators for instance?

Collapse
 
mikearnaldi profile image
Michael Arnaldi

because changing syntax would break compatibility with tools like eslint or prettier, additionally you are supposed to be able to integrate TS+ progressively in code potentially even directly for projects that have types maintained separately (so you need something that you can write in d.ts which is add only and doesn't break any standard TS/JS). TypeScript itself is progressively thinking more and more of types as comments and their recent TC39 proposal goes in that direction too. I can see how it may be perceived as non friendly to use but in practice our feeling after having built huge codebases like github.com/effect-TS/core/ with it is that it is indeed very friendly to use and those comments only end up representing a small portion of your code.

Collapse
 
patroza profile image
Patrick Roza • Edited

I was also not amused by that at first either, but because it's basically just enhancing wiring metadata (actual types are still expressed as usual), it's actually pretty good!

Collapse
 
bricecarpentier profile image
Brice Carpentier

Hey, I’m wondering what The current status of that project is as there’s been no commit on the docs or stdlib over the last year. Are you guys still using it on effect for example?