fp-ts icon indicating copy to clipboard operation
fp-ts copied to clipboard

Change zipWith signature

Open joepjoosten opened this issue 2 years ago • 5 comments

The order of the arguments of zipWith is wrong if you want to compose new functions. for example if i want to compose a 2d array zipper: const zip2d = (xs, ys) => zipWith(xs, ys, zip)

If the function for is first, then it could look like: const zip2d = zipWith(zip)

Also more in line with haskell: https://hackage.haskell.org/package/base-4.17.0.0/docs/Prelude.html#v:zipWith

Current Behavior

Current signature: export declare const zipWith: <A, B, C>(fa: A[], fb: B[], f: (a: A, b: B) => C) => C[]

Desired Behavior

Desired signatute: export declare const zipWith: <A, B, C>( f: (a: A, b: B) => C) => (fa: A[], fb: B[]) => C[]

joepjoosten avatar Dec 13 '22 22:12 joepjoosten

A pipeable signature is:

export declare const zipWith: <A, B, C>(fb: B[], f: (a: A, b: B) => C) => (fa: A[]) => C[]

that will be in V3

mikearnaldi avatar Dec 22 '22 15:12 mikearnaldi

@mikearnaldi Could we flip and curry fb and f there, matching Haskell? I don't think there's ever a case where non-unary functions are advantageous.

samhh avatar Jan 02 '23 22:01 samhh

@mikearnaldi Could we flip and curry fb and f there, matching Haskell? I don't think there's ever a case where non-unary functions are advantageous.

Inference would be seriously compromised, TypeScript is very different from Haskell and in TS non-unary functions are extremely useful. Flipping fb won't allow inference of B.

In fp-ts the pipe function is not function composition, it is a simulation of methods, the above will work as:

pipe([0, 1, 2], zipWith([3, 4, 5], (a, b) => a + b))

and can be piped with any other function of the module.

mikearnaldi avatar Jan 04 '23 08:01 mikearnaldi

Inference would be seriously compromised, TypeScript is very different from Haskell and in TS non-unary functions are extremely useful. Flipping fb won't allow inference of B.

Ah. So I find in practice that anonymous functions are rarely used. Simple stuff like that already exists in userland as a readable function (fp-ts-std add in that case), and anything much more complex should be extracted outside of the pipeline. And you can always fall back to explicit annotations.

On the other hand I often want to partially apply these types of functions, which currying enables:

import { add } from 'fp-ts-std/Number'
declare const zipWith: <A, B, C>(f: (a: A) => (b: B) => C) => (fa: A[]) => (fb: B[]) => C[]

pipe([0, 1, 2], zipWith(add)([3, 4, 5]))

const zipAdd = zipWith(add)
pipe([0, 1, 2], zipAdd([3, 4, 5]))

samhh avatar Jan 04 '23 12:01 samhh

Inference would be seriously compromised, TypeScript is very different from Haskell and in TS non-unary functions are extremely useful. Flipping fb won't allow inference of B.

Ah. So I find in practice that anonymous functions are rarely used. Simple stuff like that already exists in userland as a readable function (fp-ts-std add in that case), and anything much more complex should be extracted outside of the pipeline. And you can always fall back to explicit annotations.

On the other hand I often want to partially apply these types of functions, which currying enables:

import { add } from 'fp-ts-std/Number'
declare const zipWith: <A, B, C>(f: (a: A) => (b: B) => C) => (fa: A[]) => (fb: B[]) => C[]

pipe([0, 1, 2], zipWith(add)([3, 4, 5]))

const zipAdd = zipWith(add)
pipe([0, 1, 2], zipAdd([3, 4, 5]))

We must look at very different codebases, I find that in practice 99% of usage is directly with anonymous functions.

mikearnaldi avatar Jan 04 '23 12:01 mikearnaldi