It absoluteley is possible for typescript, here you go: type ParamsOf = T extends (...args: infer P) => any ? P : never type ReturnOf = T extends (...args: any[]) => infer R ? R : never class DoClass { do(func: T, ...args: ParamsOf): ReturnOf { return func.call(this, ...args) } } const x = new DoClass() function hallo(a: string, b: number, c: Error) {} x.do(hallo, '', 2, new Error())
@nhardy966 жыл бұрын
As of TypeScript 3.0, it is now possible to do what you want using the new and improved tuple features (albeit without JSDoc and autocomplete).
@avilde6 жыл бұрын
Great video! Certainly a great improvement to show code :)
@clo43 жыл бұрын
The Store.do method is now possible in TypeScript!
@jackninja16 жыл бұрын
Yes, thanks for listening to the feedback, great video!
@ShaneLawrence6 жыл бұрын
Well, yes, the code samples are a great addition, but where can we get the Downasaur and awkward family portrait coasters? 🤔
@FauzulChowdhury6 жыл бұрын
Something Wonderful just started in this series.
@firstprincipleslearning6 жыл бұрын
You guys are awesome ! How can we buy those coasters ?
@DanielPopeski6 жыл бұрын
That pipe operator is a beautiful beautiful thing in Elm and languages alike. It also goes hand-in-hand with partial application which is kinda covered in JS with `bind`. Trust me fellas, you would very much enjoy it if you practice even a little bit of functional style transformations.
@dassurma6 жыл бұрын
Oh we do enjoy it, but the problem is that functional programming languages are very much designed to create one-off functions on the fly while in JavaScript those create actual garbage that needs to be collected. Syntactically I _love_ pipeline, but as we outline in the video, it’s really awkward to create functions that return functions and it’s a legit performance concern.
@BennyPowers6 жыл бұрын
how big of a performance concern?
@medikoo6 жыл бұрын
I thought that this video neatly shown how ugly (in JavaScript context) it is ;-) Nevertheless let's bring ::bind! it's pure, simple, readable, and very very useful.
@thequizclassroom6 жыл бұрын
yes, it is very understandable than before, please include code example in all sessions.
@CardinalHijack4 жыл бұрын
Jake is a legend, love that dude
@wrburdick6 жыл бұрын
Partial application from the first version of |> is great but when would someone choose to use the second version to write a |> b(?,c) instead of just b(a,c)?
@dassurma6 жыл бұрын
What does your last syntax mean? The `?` denotes what parameter the left-hand side should be used as.
@wrburdick6 жыл бұрын
Sorry, I meant the last question mark as an actual question mark, not part of the syntax. I'm asking when someone would use a |> b(?, c) instead of just b(a, c) Does the |> operator do anything other than just plug the left hand side into the question mark?
@dassurma6 жыл бұрын
No, but it’s much clearer to read if you write pipelines and allows you to re-use existing functions where you don’t necessarily have control over where the argument is. E.g: `canvas |> turnToStream() |> compress(?, 'gzip') |> copyToServer('myServer.com', ?) |> putIntoCache('my-cache', ?)`
@wrburdick6 жыл бұрын
OK, that's what I wasn't seeing. Like Clojure's threading macros
@stragerneds4 жыл бұрын
Yes please! b(a,c) looks like a great solution for the video's example.
@mpcref2 жыл бұрын
One of the many reasons why I like Haxe over TypeScript. Haxe has had Static Extensions since forever. (the "using" keyword)
@FrederikDussault6 жыл бұрын
Thanks for code examples. A real nice addition to your series.
@BertVerhelst5 жыл бұрын
I wonder if we couldn't get the static analyser to detect if any square bracket operators are used on the Store object. If there are, the Store is marked as a dirty object and can't be tree shaken. If there aren't then it can be tree shaken. This would probably solve 90% of the tree shake issues. And the remaining 10% can use the more obscure solutions you mentioned in the videos. I quess it would still be an issue for certain data type classes. For instance a List class that has a shift method, but also an index operator to get a specific item. Although you could replace the List[index] with List.get(index)
@InderjitSidhux6 жыл бұрын
This is much better than before.
@BennyPowers6 жыл бұрын
what's ugly about closures and curried functions? @8:15
@victornpb6 жыл бұрын
The do() method makes "static" calls into "runtime" calls, it makes call stack unnecessarily polluted, and render autocomplete and jsdocs useless...
@wmhilton-old6 жыл бұрын
victornpb In the age of Visual Studio Code, saying "can't be defined in TypeScript" is the same as saying "breaks autocomplete".
@jakearchibald6 жыл бұрын
Which is why I want the bind operator.
@thearj92486 жыл бұрын
What if, instead of a prototype.do method, you have a prototype.add method. Ex: import { get, Store } from './keyval.js'; const theStore = new Store(); theStore.add(get); theStore.get('hello'); // It's like "do it yourself" classes. // prototype.add could be: add(method) { let name = method.name; // functions have a .name property this[name] = method; }
@adaliszk6 жыл бұрын
Why not use mixins to extend the Store class with whatever function is needed?
@wmhilton-old6 жыл бұрын
AdaLiszk TypeScript supports mixins, so that could work!
@adaliszk6 жыл бұрын
@William Hilton: I know, but in general mixins are part of the ECMAScript and a lot of other tools like Polymer uses it to extend its functionalities and have the tree-shaking work ;)
@dassurma6 жыл бұрын
How do you use mixins with instances? Or am I misunderstanding you?
@jakearchibald6 жыл бұрын
Mixins that only exist in a particular scope would be interesting, but pretty complicated. It feels like bind is just simpler.
@adaliszk6 жыл бұрын
@Surma: I would create my own class for my own needs using the building blocks provided the library then after I'm done my application I can remove the mixins one-by-one and ran the test to see which method is used in which cases. For reflecting the instance I can override the inner states to be constants in my own class, that way I can extend that even further in parts of the system where it actially needed.
@Hector-bj3ls5 жыл бұрын
You can do that in TypeScript now. function do(doFn: (...args: A) => R, ...args: A): R { return doFn(...args); }
@amatiasq5 жыл бұрын
works nicely, sadly argument names are missing though I'd use it in a pet project anyway
@Hector-bj3ls5 жыл бұрын
@@amatiasq Yes, that's the only real problem with it now. It would be a nice little improvement from the TypeScript team if they were to implement it.
@Luxalpa4 жыл бұрын
@@amatiasq `do any >(func: T, ...args: Parameters): ReturnType` works with argument names as well.
@brendanwhiting12356 жыл бұрын
10:40 Why isn't it possible with typescript? Could it just be the "any" type?
@jakearchibald6 жыл бұрын
The 'any' type is pretty much an opt-out of typescript when used in this way.
@valikhanakhmedov53445 жыл бұрын
Great talk guys, did you manage to make .do approach work with typescript though?
@saadabbasi20636 жыл бұрын
Thank you sooo much, i have been scratching my head around this for around 3 days now, but honestly Store.do is what i liked the most, unfortunately will have to opt out the types 🙃
@feldinho6 жыл бұрын
I don't see why can't the set function receive 3 arguments... it's easier to grasp than almost all the alternatives. Other than that, partial application is a beautiful thing once you get the hang of it! Right now it's not the more performant way but I'm sure the engines will optimize them in no time!
@wmhilton-old6 жыл бұрын
I ran into this very problem in isomorphic-git. At one point I had a constructor `let repo = new Git({fs, dir})` and instance methods... but after realizing some of the methods added _serious_ weight (100kb?!) I was determined to allow users to only import the methods they used. Ultimately I ended up with an API where the state management is external to the library. Instead of providing a constructor, I just provide all the operations as functions and make the user pass certain required arguments that I would have stored on "this" every call. `let result = fetch({fs, dir, ...args})`
@xaviergonz2446 жыл бұрын
It can be done in TS 2.8, but you would need to wrap the args inside an array type ArgsOf any> = T extends (this: any) => any ? never : T extends (this: any, a1: infer A1) => any ? [A1] : T extends (this: any, a1: infer A1, a2: infer A2) => any ? [A1, A2] : T extends (this: any, a1: infer A1, a2: infer A2, a3: infer A3) => any ? [A1, A2, A3] : T extends (this: any, a1: infer A1, a2: infer A2, a3: infer A3, a4: infer A4) => any ? [A1, A2, A3, A4] : // create overrides up to A10 for example any[]; class Store { do any>(func: F): ReturnType; // no args do any>(func: F, args: ArgsOf): ReturnType; // args in an array do(func: (this: Store, ...args: any[]) => R, ...args: any[]): R { // base generic case return func.call(this, ...args); } } function set(this: Store, x: number, y: string) { // do something } function clear(this: Store) { // do something } function anotherClassSet(this: string, x: number, y: string) { // do something } const store = new Store(); store.do(set, [ 'ho', 'hi' ]); // won't compile, invalid arg type store.do(set, [ 5 ]); // won't compile, too few args store.do(anotherClassSet, [ 5, 'hi' ]); // won't compile, not a method for the Store class store.do(set, [ 5, 'hi', 8 ]); // won't compile, too many args store.do(set); // won't compile store.do(clear, []); // won't compile store.do(set, [ 5, 'hi' ]); // ok! store.do(clear); // ok!
@xaviergonz2446 жыл бұрын
Assuming TS ever adds support for ...args: [number, string] or some such then it would be possible without the array wrapping
@dassurma6 жыл бұрын
Thanks for the detailed example!
@xaviergonz2446 жыл бұрын
Another try, works as intended (the params do not need to be wrapped in an array), but it requires to add the possible methods that can be invoked to the "do" signature type FuncSignature = T extends (this: any) => infer R ? (fn: T) => R : T extends (this: any, a1: infer A1) => infer R ? (fn: T, a1: A1) => R : T extends (this: any, a1: infer A1, a2: infer A2) => infer R ? (fn: T, a1: A1, a2: A2) => R : T extends (this: any, a1: infer A1, a2: infer A2, a3: infer A3) => infer R ? (fn: T, a1: A1, a2: A2, a3: A3) => R : T extends (this: any, a1: infer A1, a2: infer A2, a3: infer A3, a4: infer A4) => infer R ? (fn: T, a1: A1, a2: A2, a3: A3, a4: A4) => R : // add more... T extends (this: any, ...args: any[]) => infer R ? (fn: T, ...args: any[]) => R : never; class Store { do: FuncSignature & FuncSignature = (func: Function, ...args: any[]) => { return func.call(this, ...args); }; } function set(this: Store, x: number, y: string) { // do something } function clear(this: Store) { // do something } function anotherClassSet(this: string, x: number, y: string) { // do something } const store = new Store(); store.do(set, 'ho', 'hi'); // won't compile, invalid arg type store.do(set, 5); // won't compile, too few args store.do(anotherClassSet, 5, 'hi'); // won't compile, not a method for the Store class store.do(set, 5, 'hi', 8); // won't compile, too many args store.do(set); // won't compile store.do(clear, []); // won't compile store.do(set, 5, 'hi'); // ok! store.do(clear); // ok!
@jakearchibald6 жыл бұрын
This gets even more complicated if any of the "methods" have a generic element to them
@dassurma6 жыл бұрын
Yeah, but who would do _that_? Come on.
@ozone19795 жыл бұрын
I wish there was a way to simply import a whole class or object, like Jquery and have it be tree shaken.
@nabbydude6 жыл бұрын
There is a proposal for Variadic Kinds on the TypeScript Github (#5453) that would allow rest parameters to be defined as tuples, that (maybe with a little help from the new conditional types in TS 2.8) would make this possible. Its theoretically possible right now with an arbitrary amount of overloads, but that would be super ugly. Another option would be `store.do(set)("hello", "world")`, as `store.do(set)` would have the exact same signature as `set` itself.
@omri93256 жыл бұрын
It can be achieved with overloads and be ugly, but who cares if it's being imported from a module/library that you as a user don't see it's code?
@roceb50096 жыл бұрын
Here I thought you were going to talk about how Rangers can take a feat to scare birds out of their nests...
@CalvinMannedUp6 жыл бұрын
Bit late to the party. But how about import { get, set, Store } from './idb-keyval.js'; const store = Object.assign(new Store('my-store'), { get, set });
@amatiasq5 жыл бұрын
well you'll have to do that for each instance and it's up to the devs to include only the methods that they are going to use
@swanbosc53716 жыл бұрын
hey... great vid !!! Just to know... Can Typescript tree-shake class ?
@dassurma6 жыл бұрын
As explained in the video: There’s no fool-proof way to prove that some dynamic code doesn’t invoke a method,
@odynnxd6 жыл бұрын
Loved the video, this longs talks are great!
@emilemil16 жыл бұрын
Wouldn't it be easier to just make the methods static and pass in an instance? Something like: let store = new Store(); Store.set(store, "hello", "world"); And the set method would be: static set(instance, key, val) { return instance._underlyingDB.setKeyVal(key, val); } Or, replicating the paradigm for the database: static set(instance, key, val) { return UnderlyingDB.setKeyVal(instance._underlyingDB, key, val); } No new syntax needed. Build tools could probably convert most regular code into this style of code automatically, as long as you're not calling methods by strings.
@jakearchibald6 жыл бұрын
Static methods don't tend to tree-shake. What I went for in the end was set(key, val, { store }).
@returnzero74923 жыл бұрын
Hey guys! Just want to drop an example. It is possible to do the 'do' function in typescript now. const kRecords = Symbol('records') export type StoreFunction = (...args: any) => any export class Store { public [kRecords]: Record constructor() { this[kRecords] = {} } do(fn: S, ...args: Parameters): ReturnType { return fn.apply(this, args) } } export function get(this: Store, key: string) { return this[kRecords][key] } export function set(this: Store, key: string, value: T) { this[kRecords][key] = value } // Meant to be in another file const main = () => { const store = new Store() store.do(set, 'Hello', 'world!') store.do(get, 'Hello') } if (true) { main() }
@BennyPowers6 жыл бұрын
@10:40 TypeScript: "It's definitely more complicated than it should be" 🤣
@JoVandenBerghe6 жыл бұрын
Great video! You could also solve the reversed order of nested arguments: h(g(f())) with a pipe function (not operator) that accepts 'pipeable functions': pipe(f(), g(), h()), like it is done in RxJS or callbags. This can be strongly typed without Variadic Types. see following code sample: You can copy this to the 'Typescript playground' and change the order of the pipeable functions and you will get errors. /** describes a function with one argument */ type TFn = (arg: TIn) => TOut /** * Pipe function as alternative of pipe operator: |> * Pipe accepts pipeable functions * A pipeable function is a function that returns a function that only accepts 1 argument (= the result of the previous function) */ function pipe(startValue: TIn, fn1: TFn): TOut; function pipe(startValue: TIn, fn1: TFn, fn2: TFn): TOut; function pipe(startValue: TIn, fn1: TFn, fn2: TFn, fn3: TFn): TOut; function pipe(startValue: TIn, fn1: TFn, fn2: TFn, fn3: TFn, fn4: TFn): TOut; function pipe (startValue: any, ...fs: Function[]) { return fs.reduce((prev, current) => current(prev), startValue); } // Some sample pipeable functions that change types const parseNumber = () => (s: string) => parseFloat(s); // string -> number const plus = (valueToAdd:number) => (num: number) => num + valueToAdd; // number -> number const formatCurrency = (prefix: string, suffix = "") => (valuta: number) => `${prefix}${valuta}${suffix}`; // number -> string // A pipe example const result = pipe( "1", // "1" parseNumber(), // 1 plus(2), // 3 formatCurrency("€"), // "€3" ) alert(result); // €3
@marcelmundl90122 жыл бұрын
Here is the TS implementation: ```ts function do(this: TThisType, f: (this: TThisType, ...args: TArgs) => TReturnType, ...args: TArgs) { return f.call(this, ...args); } ``` You would use it like this: ```ts function set(this: Store, key: string, value: string) { this._innerDb.set(key, value); } class Store { _innerDb = LocalStorage; do = do; } const store = new Store(); store.do(set, "foo", "bar"); ``` The only (but IMHO significant) downside to this is that members of Store cannot be private anymore. You would essentially be stuck with classes which behave like structs in C.
@hyyanabofakher62296 жыл бұрын
Great video , Keep it up 👍
@PhalgunVaddepalli3 жыл бұрын
Based on a tree story!
@tankian88296 жыл бұрын
To be honest, I don't feel the `set` function that takes a function and returns a function used with pipe operator is ugly.
@dassurma6 жыл бұрын
No, as I said, the ugliness is hidden. But if you have to _develop_ the functions that are supposed to be used with |>, it gets ugly (and produces a bit of memory garbage that :: avoids).
@andimclean6 жыл бұрын
@suma Love the coster with you both on. Can I have one? :)
@talgatsaribayev8216 жыл бұрын
Thanks for video and code(finally :)). I understand the problems it will resolve. However I would like raise the concerns that Dwight House have. And add that there is proposal for private class methods, however in my opinion it useless. Because by this video you need to treat all methods as a private. So maybe old fashion function and object are more suitable?
@XiXora6 жыл бұрын
9:10 "…in the pipeline" 😏
@dwighthouse6 жыл бұрын
If you are going to change your classes to externalize the methods outside the class, and since JS classes can't natively support private data anyway, what's the point of using classes at all? Why not define your program in terms of data structures made of objects, and bare functions that operate on those structures, either purely or with mutations? Is there a way for a class to define what functions the pipeline operator (or the now-dead bind operator) can add, or can literally any code inject a method into a class? If the latter, you don't even have legitimate behavioral separation, so why pretend you do by using classes? Certainly, a `do` method on a class will remove all potential data integrity.
@dassurma6 жыл бұрын
JavaScript is dynamically typed. Using that against a proposal seems not very useful (you have always been able to pass any object to any function). What’s the point of classes? Grouping data and the functions that operate on that data. You can invoke the method of one class instance on a completely unrelated class instance right now, so that’s not anything new. These 2 proposal just make it more obvious that _that_ is what’s actually happening. It’s not about was is allowed in the language, but about what conveys the intention of the developer most intuitively to another reader.
@dwighthouse6 жыл бұрын
Just as you can pass any object to any function, you can also pass any object to any method on a class, right? I don't see how this is an argument against using plain objects and functions. Classes share the exact same potential vulnerability. I understand that classes provide a way to group behavior and data together. But if grouping related data and behavior together is the focus, why not just create a plain object that contains your data and references (via keys) to functions you want related to the data? The only difference, functionally, is that you explicitly pass the object to the function, rather than implicitly relying on the user to use `new` and `this` properly. Both this system and class-based systems provide neither data privacy nor method security, so they are equivalent security-wise. From my perspective, the idea of splitting the method behavior outside of the classes at all (via pipeline or bind) seems to be itself a violation of the intention of classes, because the method is actually just a function that anything could potentially use for themselves, rather than something that is strictly for that class. My proposal is to dispense with the subterfuge: functions that operate on objects correctly represent the security available to the API (that is, none) to the end user without wrapping it up in classes which make the code appear to have a strict API when it actually doesn't.
@GiorgioGamberoni6 жыл бұрын
Thx for the code :)
@CTimmerman6 жыл бұрын
But Google offered its own copy of Jquery on its CDN so it would be cached for all but the first of the sites that use that, and on the Jquery site itself you can make a lib with only the functions you want. Ah, it's about variable scope... and horrible syntax that reminds me of Lisp, Perl, and other languages most people avoid due to lack of readability.
@jan.melcher6 жыл бұрын
This should be possible now in Typescript 3.0 with genreric tuple spread/rest www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html
@ryanmann97752 жыл бұрын
Classes in JavaScript aren't real, they're just objects with functions on prototype with their calling context bound to the instance of the object. That said, I'd argue not using classes at all, especially if you're using typescript. Instead just use modules and namespaces.
@Khalyomede6 жыл бұрын
I do not like .do operator because it means it will privatize it... the pipeline operator is so ugly in the implementation, too complicated, not as straight forward as the bind operator. I do not see any trace of the bind operator in the TC39 github proposal repo. Any clues?
@RafaelCouto6 жыл бұрын
Won't symbols solve this problem?
@jakearchibald6 жыл бұрын
I can't see how
@RafaelCouto6 жыл бұрын
Something like this perhaps? It won't clash stores and can be "tree shaken" XD => jsbin.com/verimut/1/edit?js
@alexeypavlov53406 жыл бұрын
Guys, please add subtitles. Auto subtitles very bad and I much more worse in English.
@dassurma6 жыл бұрын
We always have our videos captioned. It might take a day or two for those to be finished, tho.
@khaled_osman4 жыл бұрын
Please do a video about how good and important it is refactor code and keep your dependencies up to date.. I have a hard time convincing my managers everywhere I work, they don't see a "business value", but get anxiety attacks about things breaking everytime I want to change something, refactor some code to follow a best practice, improve performance, or update/migrate to the latest versions of dependencies
@DivjotSingh6 жыл бұрын
No... What? Pipeline operator is beautiful and opens up a new world of functional programming (partial application, chaining of methods/promises). Like your usecase isn't even the idomatic usage of pipeline operator, yet it could help you there. It's more versatile. You are more likely to keep a function on RHS than a higher order function, it's just the RxJS community that loves functional programming a lot, and already has its codebase written in a way to support it. You can always rename original set function as `createSetter` and store its result in a `set` variable to make it less convoluted. I agree that bind operator is elegant in this use case, coz set method was already using `this`, which can itself be ugly, but if you're going to dissect out all methods of class as functions, you can probably consider functional programming paradigm altogether. Also, the other common usecase for bind operator would've been in case of React components. I feel public class fields solve it in a much better way and are already baked-in under a flag, so that kind of makes bind operator less useful. For all other cases (which I feel would be rare), using call/apply wouldn't hurt. Sorry for being super opinionated about this. Loved the code examples and the topic as whole. `do`approach seems really nice! How about creating different classes and extending Store with it? Basically using inheritance to extend features of base class? E.g. BasicStore & EnhancedStore are exported by idb-keyval, user may use either based on appetite.
@dassurma6 жыл бұрын
That’s what we have been discussing in the spec issue as well. I agree - this tree-shaking problem is not idiomatic for pipeline operator. The problem is that currently _either_ pipeline _or_ bind will happen, not both. This might change, but that’s the premise we used in this video. With that in mind, I think the problems `::` solves are currently much more common than the problems `|>` solves. You _can_ shoe-horn the problems space of one operator onto the other, but then weird things happen, as we outline in the video.
@zanehannan53066 жыл бұрын
If only there could be both... Also I agree; the this::bind operator is a great thing. Being able to bind without making garbage... sweet.
6 жыл бұрын
Man, JavaScript is such as mess that developers need to think about things like this.
@frisoflo6 жыл бұрын
Great video, but all proposals have different flavors of beeing sh*tty :) A Tree Shaker is (should be) able to remove unused methods. I dont see the problem.
@jakearchibald6 жыл бұрын
For the reasons mentioned in the video: it isn't statically analysable.
@frisoflo6 жыл бұрын
okay. I looked the video again. They mentioned that each method is exported individually. So Rollup & co can use a simple approach to remove unused imports. But a Code analysor knows the callgraph of the whole code and can remove unused methods (and classes). More compilcated, because the code needs to be analyzed but not impossible.
@jakearchibald6 жыл бұрын
What about the instance[str.split('').reverse().join('')] example in the video?
@frisoflo6 жыл бұрын
Yes, right - no chance for a code analysor. I silently assumed that this kind of "Reflection" stuff (counterpart in Java or C#) will not be used. (I come Java/C#/C++ and would accept that dead code remover would fail in this case)
@DoctorReefer6 жыл бұрын
How bout we all just stop using classes in our code? Problem solved.
@MartinHodgkins6 жыл бұрын
Ah, I am not being fooled again. This is an April fools joke right. Nobody would ever believe this, not even funny.