HackerTrans
TopNewTrendsCommentsPastAskShowJobs

upzylon

no profile record

Submissions

A generically typed pipe function in TypeScript

github.com
137 points·by upzylon·4 tahun yang lalu·51 comments

Show HN: Froebel–a strictly typed utility library for Deno, Node, and Browsers

github.com
16 points·by upzylon·4 tahun yang lalu·2 comments

Show HN: A website where you can watch and listen to sorting algorithms

sort.bullinger.dev
8 points·by upzylon·4 tahun yang lalu·4 comments

Show HN: A little multithreaded ray tracer written in a few lines of TypeScript

github.com
2 points·by upzylon·4 tahun yang lalu·0 comments

Show HN: SnatchBlock – a strictly typed TypeScript utility library (WIP)

github.com
1 points·by upzylon·5 tahun yang lalu·0 comments

LCH colors in CSS: what, why, and how?

lea.verou.me
4 points·by upzylon·5 tahun yang lalu·0 comments

comments

upzylon
·4 tahun yang lalu·discuss
applyPipe is added now: https://github.com/MathisBullinger/froebel#applypipe

It's included in version 0.21.0
upzylon
·4 tahun yang lalu·discuss
It's just an identifier like any other defined in types.ts: https://github.com/MathisBullinger/froebel/blob/main/types.t...

It's defined as

  type λ<TA extends any[] = any[], TR = any> = (...args: TA) => TR;
meaning λ is just any function and λ<[number], string> would be the type of a function that takes a number as its only argument and returns a string.
upzylon
·4 tahun yang lalu·discuss
Thanks! Yes, it should work with any number of arguments. Or at least in theory it does, it seems that around 47 chained functions TypeScript will give up and stops computing the type. It's written in TypeScript v4.7.2 but I didn't use any recently introduced features, so it should also work a few version back. How far back exactly, I'd need to check.
upzylon
·4 tahun yang lalu·discuss
There isn't necessarily any issue with it besides maybe not being very maintainable. I just tried to find a generic solution for the fun of it
upzylon
·4 tahun yang lalu·discuss
Looks like they define pipe separately for every number of arguments: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast...
upzylon
·4 tahun yang lalu·discuss
Chaining assignments and using non-ascii characters as variable names is definitely not the norm in most TypeScript projects I've seen and worked on.

When I started this project it was just for my own personal use and kind of a creative / intellectual outlet for me where I didn't have to make any compromises and adhere to anyone else's style. So to this day some of the code might be a bit... exotic. It's definitely not how I'd write it in my day job.

That's the JS part. The type-definitions I'm afraid are just unreadable because it's TypeScript. I feel they're like regexes in that regard: write once and try not to touch them again.
upzylon
·4 tahun yang lalu·discuss
Yes, every function except the first one needs to have exactly one argument.

Others have also said that `pipe` is not an ideal name for the function. So perhaps renaming it to `compose` and have `pipe` be of the form `pipe(arg, funA, funB, ...)` (like you suggested for `applyPipe`) might be the solution. Will need to think about it a bit more.
upzylon
·4 tahun yang lalu·discuss
Thanks for linking the code, I meant to link to the pipe section of the README but obviously botched that and can't change it now :)

I was really exited about the pipe proposal and actively followed the discussion like 4 years ago. But with every passing year of no progress my excitement is slowly dying.

An `applyPipe` function is an interesting idea. My only concern with it is that the first function in the pipe could have more (or less) than one argument. And in that case how would you know where the arguments stop and the functions begin? Maybe the first parameter should be an array of the arguments. Or applyPipe could be of the form applyPipe(1, 2)(add, square, whatever). What do you think? If there is interest in this I'll add it.
upzylon
·4 tahun yang lalu·discuss
That looks interesting, I hadn't heard of the project. Will definitely have a look at the library.

But it defines its pipe function the same way RXJS does, separately for any number of arguments:

  function pipe<A>(a: A): A
  function pipe<A, B>(a: A, ab: (a: A) => B): B
  function pipe<A, B, C>(a: A, ab: (a: A) => B, bc: (b: B) => C): C
  function pipe<A, B, C, D>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D
  ...
https://github.com/gcanti/fp-ts/blob/master/src/function.ts#...
upzylon
·4 tahun yang lalu·discuss
When programming in a functional style, quite of often I find myself wanting to rewrite nested function calls as a chain. Hopefully at some point the proposed pipe operator will make it into JavaScript and TypeScript but for now defining a pipe function will have to do. The function should allow rewriting something like

  double(square(half(2)))
as

  pipe(half, square, double)(2)
For a while now I've struggled to implement the type definition for that function, so that every passed-in function can only accept the return type of the previous function as its parameter and the resulting function will take the arguments of the first function as its parameters and return the type of the last function.

The main problem with this is that trying to define a type for pipe's arguments up-front would require typing them as an infinitely-recursive type that TypeScript cannot handle. A common workaround for this is to define pipe's type separately for every number of arguments it can take. This is for example how RXJS defines its pipe function: https://github.com/ReactiveX/rxjs/blob/f174d38554d404f21f98a...

Other common solutions are to make concessions like requiring all functions to have the same return type or letting pipe only take one function at a time and returning an object with methods to add another function and invoke the chain. None of these solutions are satisfying in my opinion.

I think I've finally found an implementation that fulfills all these criteria. The argument and return types of passed in functions are correctly enforced (no matter the number of passed-in functions) and the pipe function returns a function that accepts the arguments of the first function, invokes all functions in turn with the result of the last, and correctly returns the type of the last function of the chain. Including asynchronous functions in the chain works to: if a function returns a promise that promise is resolved before being passed into the next function and the function returned from pipe will return a promise as its type.

There is one disadvantage to the implementation that I'm aware of: When passing in anonymous functions, the types of their arguments can not be inferred if they aren't annotated. That means that

  pipe(() => 10, n => n.toString())
would return the type

  () => any
but I think that's an acceptable tradeoff because when annotated

  pipe(() => 10, (n: number) => n.toString())
it will return the correct type

  () => string


Thought the implementation might be worth sharing here in case it's useful to someone else and because it's an interesting problem to solve in TypeScript's type system.

If you have any suggestions on how to improve the function's typing or know of any better implementations, I'd appreciate it if you would let me know!

Edit: I meant to link to the pipe part of the readme, but I see the link is just to the repo, that's unfortunate.

Here is the relevant section of the readme: https://github.com/MathisBullinger/froebel#pipe

and here the implementation: https://github.com/MathisBullinger/froebel/blob/main/pipe.ts...
upzylon
·4 tahun yang lalu·discuss
Any feedback you might have would be appreciated. The library contains a bunch of utilities that I reuse between projects and thought might be worth sharing. What makes this library different from other utility libraries like e.g. Lodash is that I try to type arguments and return types as narrowly as possible. E.g.

    partition([1,'a',2,'b',3], (el): el is string => typeof el === 'string')
will return tuples of the type ['a','b'] and [1,2,3], not just generic arrays of type string[] and number[].

I just migrated the module to Deno and rewrote the test cases using the built-in Deno test runner. Also contributed a small bug fix to the test runner that I encountered during the migration.
upzylon
·5 tahun yang lalu·discuss
There definitely are valid use cases for a Map, and even more now that we have WeakMaps (e.g. associating data DOM nodes). Or mapping functions to values, e.g. something like this:

  const returnCache = new Map<() => any, any>()

  const memoized = <T extends () => any>(f: T): ReturnType<T> => {
    if (returnCache.has(f)) return returnCache.get(f)
    const v = f()
    returnCache.set(f, v)
    return v
  }
upzylon
·5 tahun yang lalu·discuss
If the problem is just that you want to use non-primitive keys, that's what Maps are for.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...