Skip to content

Commit

Permalink
Allow dual to support optional args (Effect-TS#262)
Browse files Browse the repository at this point in the history
  • Loading branch information
IMax153 authored Mar 7, 2023
1 parent 7121c64 commit dd3b814
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/ten-rules-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/data": patch
---

allow dual to support optional args
8 changes: 4 additions & 4 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
inputs = {
nixpkgs = {
url = "github:nixos/nixpkgs/nixos-22.11";
url = "github:nixos/nixpkgs/nixpkgs-unstable";
};
};

Expand Down
50 changes: 38 additions & 12 deletions src/Function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,27 @@ export interface FunctionTypeLambda extends TypeLambda {
export const isFunction = (input: unknown): input is Function => typeof input === "function"

/**
* Creates a function that can be used in a data-last (aka `pipe`able) or data-first style.
* Creates a function that can be used in a data-last (aka `pipe`able) or
* data-first style.
*
* @param arity - The arity of the uncurried function.
* The first parameter to `dual` is either the arity of the uncurried function
* or a predicate that determines if the function is being used in a data-first
* or data-last style.
*
* Using the arity is the most common use case, but there are some cases where
* you may want to use a predicate. For example, if you have a function that
* takes an optional argument, you can use a predicate to determine if the
* function is being used in a data-first or data-last style.
*
* @param arity - Either the arity of the uncurried function or a predicate
* which determines if the function is being used in a data-first
* or data-last style.
* @param body - The definition of the uncurried function.
*
* @example
* import { dual, pipe } from "@effect/data/Function"
*
* // Exampe using arity to determine data-first or data-last style
* export const sum: {
* (that: number): (self: number) => number
* (self: number, that: number): number
Expand All @@ -44,25 +57,38 @@ export const isFunction = (input: unknown): input is Function => typeof input ==
* assert.deepStrictEqual(sum(2, 3), 5)
* assert.deepStrictEqual(pipe(2, sum(3)), 5)
*
* // Example using a predicate to determine data-first or data-last style
* export const sum2: {
* (that: number): (self: number) => number
* (self: number, that: number): number
* } = dual((args) => args.length === 1, (self: number, that: number): number => self + that)
*
* assert.deepStrictEqual(sum(2, 3), 5)
* assert.deepStrictEqual(pipe(2, sum(3)), 5)
*
* @since 1.0.0
*/
export const dual = <
DataLast extends (...args: Array<any>) => any,
DataFirst extends (...args: Array<any>) => any
>(
arity: Parameters<DataFirst>["length"],
body: DataFirst
): DataLast & DataFirst => {
// @ts-expect-error
export const dual: {
<DataLast extends (...args: Array<any>) => any, DataFirst extends (...args: Array<any>) => any>(
arity: Parameters<DataFirst>["length"],
body: DataFirst
): DataLast & DataFirst
<DataLast extends (...args: Array<any>) => any, DataFirst extends (...args: Array<any>) => any>(
isDataFirst: (args: IArguments) => boolean,
body: DataFirst
): DataLast & DataFirst
} = (arity, body) => {
const isDataFirst: (args: IArguments) => boolean = typeof arity === "number" ?
((args) => args.length >= arity) :
arity
return function() {
if (arguments.length >= arity) {
if (isDataFirst(arguments)) {
// @ts-expect-error
return body.apply(this, arguments)
}
return ((self: any) => body(self, ...arguments)) as any
}
}

/**
* Apply a function to a given value.
*
Expand Down

0 comments on commit dd3b814

Please sign in to comment.