Skip to content

Commit 1185228

Browse files
committed
update curry docs
1 parent 99b2141 commit 1185228

File tree

1 file changed

+31
-7
lines changed

1 file changed

+31
-7
lines changed

doc/features.md

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -995,16 +995,19 @@ Things missing from the standard library.
995995
- Hence it doesn't matter that the memo lives in the ``memoized`` closure on the class object (type), where the method is, and not directly on the instances. The memo itself is shared between instances, but calls with a different value of ``self`` will create unique entries in it.
996996
- For a solution that performs memoization at the instance level, see [this ActiveState recipe](https://github.com/ActiveState/code/tree/master/recipes/Python/577452_memoize_decorator_instance) (and to demystify the magic contained therein, be sure you understand [descriptors](https://docs.python.org/3/howto/descriptor.html)).
997997
- `curry`, with some extra features:
998-
- Passthrough on the right when too many args (à la Haskell; or [spicy](https://github.com/Technologicat/spicy) for Racket)
999-
- If the intermediate result of a passthrough is callable, it is (curried and) invoked on the remaining positional args. This helps with some instances of [point-free style](https://en.wikipedia.org/wiki/Tacit_programming).
1000-
- For simplicity, all remaining keyword args are fed in at the first step that has too many positional args.
998+
- **Changed in v0.15.0.** `curry` supports both positional and named arguments, and binds arguments to function parameters like Python itself does. The call triggers when all parameters are bound, regardless of whether they were passed by position or by name, and at which step of the currying process they were passed.
999+
- **Changed in v0.15.0.** `unpythonic`'s multiple-dispatch system (`@generic`, `@typed`) is supported. `curry` looks for an exact match first, then a match with extra args/kwargs, and finally a partial match. If there is still no match, this implies that at least one parameter would get a binding that fails the type check. In such a case `TypeError` regarding failed multiple dispatch is raised.
1000+
- **Changed in v0.15.0.** If the function being curried is `@generic` or `@typed`, or has type annotations on its parameters, the parameters being passed in are type-checked. A type mismatch immediately raises `TypeError`. This helps support [fail-fast](https://en.wikipedia.org/wiki/Fail-fast) in code using `curry`.
1001+
- Passthrough when too many args (à la Haskell; or [spicy](https://github.com/Technologicat/spicy) for Racket). Positional args are passed through **on the right**.
1002+
- If the intermediate result of a passthrough is callable, it is (curried and) invoked on the remaining args and kwargs. This helps with some instances of [point-free style](https://en.wikipedia.org/wiki/Tacit_programming).
10011003
- If more positional args are still remaining when the top-level curry context exits, by default ``TypeError`` is raised.
10021004
- To override, set the dynvar ``curry_context``. It is a list representing the stack of currently active curry contexts. A context is any object, a human-readable label is fine. See below for an example.
10031005
- To set the dynvar, `from unpythonic import dyn`, and then `with dyn.let(curry_context=...):`.
1006+
- Even with the upgrades in v0.15.0, passing through *named* args to an outer curry context is not supported. This may or may not change in the future; fixing this requires support for named return values. See issue [#32](https://github.com/Technologicat/unpythonic/issues/32).
10041007
- Can be used both as a decorator and as a regular function.
10051008
- As a regular function, `curry` itself is curried à la Racket. If it gets extra arguments (beside the function ``f``), they are the first step. This helps eliminate many parentheses.
1006-
- **Caution**: If the positional arities of ``f`` cannot be inspected, currying fails, raising ``UnknownArity``. This may happen with builtins such as ``list.append``.
1007-
- `partial` with run-time type checking, which helps a lot with fail-fast in code that uses partial application. Type-checks arguments against type annotations, then delegates to `functools.partial`. Supports `unpythonic`'s `@generic` and `@typed` functions. Our `curry` uses this type-checking `partial` instead of the standard one, so currying supports fail-fast, too. **Added in v0.15.0.**
1009+
- **Caution**: If the signature of ``f`` cannot be inspected, currying fails, raising ``ValueError``, like ``inspect.signature`` does. This may happen with builtins such as ``list.append``, ``operator.add``, ``print`` or ``range``.
1010+
- **Added in v0.15.0.** `partial` with run-time type checking, which helps a lot with fail-fast in code that uses partial application. This function type-checks arguments against type annotations, then delegates to `functools.partial`. Supports `unpythonic`'s `@generic` and `@typed` functions, too.
10081011
- `composel`, `composer`: both left-to-right and right-to-left function composition, to help readability.
10091012
- Any number of positional arguments is supported, with the same rules as in the pipe system. Multiple return values packed into a tuple are unpacked to the argument list of the next function in the chain.
10101013
- `composelc`, `composerc`: curry each function before composing them. Useful with passthrough.
@@ -1150,7 +1153,9 @@ Finally, keep in mind this exercise is intended as a feature demonstration. In p
11501153
11511154
#### ``curry`` and reduction rules
11521155
1153-
The provided variant of ``curry``, beside what it says on the tin, is effectively an explicit local modifier to Python's reduction rules, which allows some Haskell-like idioms. When we say:
1156+
**Changed in v0.15.0.** *`curry` now supports kwargs, too, and binds parameters like Python itself does. Also, `@generic` and `@typed` functions are supported.*
1157+
1158+
Our ``curry``, beside what it says on the tin, is effectively an explicit local modifier to Python's reduction rules, which allows some Haskell-like idioms. Let's consider a simple example with positional arguments only. When we say:
11541159
11551160
```python
11561161
curry(f, a0, a1, ..., a[n-1])
@@ -1167,7 +1172,26 @@ it means the following. Let ``m1`` and ``m2`` be the minimum and maximum positio
11671172
- If ``n < m1``, partially apply ``f`` to the given arguments, yielding a new function with smaller ``m1``, ``m2``. Then curry the result and return it.
11681173
- Internally we stack ``functools.partial`` applications, but there will be only one ``curried`` wrapper no matter how many invocations are used to build up arguments before ``f`` eventually gets called.
11691174
1170-
In the above example:
1175+
As of v0.15.0, the actual algorithm by which `curry` decides what to do, in the presence of kwargs and `@generic` functions, is:
1176+
1177+
- If `f` is **not** `@generic` or `@typed`:
1178+
- Compute parameter bindings of the args and kwargs collected so far, against the call signature of `f`.
1179+
- Note we keep track of which arguments were passed positionally and which by name. To avoid subtle errors, they are eventually passed to `f` the same way they were passed to `curry`. (Positional args are passed positionally, and kwargs are passed by name.)
1180+
- If there are no unbound parameters, and no args/kwargs are left over, we have an exact match. Call `f` and return its result, like a normal function call.
1181+
- Any sequence of curried calls that ends up binding all parameters of `f` triggers the call.
1182+
- As before, beware when working with variadic functions. Particularly, keep in mind that `*args` matches **zero or more** positional arguments (as the [Kleene star](https://en.wikipedia.org/wiki/Kleene_star)-ish notation indeed suggests).
1183+
- If there are no unbound parameters, but there are args/kwargs left over, arrange passthrough for the leftover args/kwargs (that were rejected by the call signature of `f`), and call `f`. If the result is a callable, curry it, and recurse. Else form a tuple... (as above).
1184+
- If neither of the above match, we know there is at least one unbound parameter, i.e. we have a partial match. Keep currying.
1185+
- If `f` is `@generic` or `@typed`:
1186+
- Iterate over multimethods registered on `f`, **up to three times**.
1187+
- First, try for an exact match that passes the type check. **If any such match is found**, pick that multimethod. Call it and return its result (as above).
1188+
- Then, try for a match that passes the type check, but has extra args/kwargs. **If any such match is found**, pick that multimethod. Arrange passthrough... (as above).
1189+
- Then, try for a partial match that passes the type check. **If any such match is found**, keep currying.
1190+
- If none of the above match, it implies that no matter which multimethod we pick, at least one parameter would get a binding that fails the type check. Raise `TypeError`.
1191+
1192+
(If *really* interested in the gritty details, look at the source code of `unpythonic.fun.curry`. It calls some functions from `unpythonic.dispatch` for its `@generic` support, but otherwise it's pretty much self-contained.)
1193+
1194+
Getting back to the simple case, in the above example:
11711195
11721196
```python
11731197
curry(mapl_one, double, ll(1, 2, 3))

0 commit comments

Comments
 (0)