You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: doc/features.md
+31-7Lines changed: 31 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -995,16 +995,19 @@ Things missing from the standard library.
995
995
- 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.
996
996
- 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)).
997
997
-`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 iscallable, 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 iscallable, 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).
1001
1003
- If more positional args are still remaining when the top-level curry context exits, by default ``TypeError``is raised.
1002
1004
- To override, set the dynvar ``curry_context``. It is a list representing the stack of currently active curry contexts. A context isanyobject, a human-readable label is fine. See below for an example.
1003
1005
- 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 isnot 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).
1004
1007
- Can be used both as a decorator andas a regular function.
1005
1008
- 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.
1008
1011
-`composel`, `composer`: both left-to-right and right-to-left function composition, to help readability.
1009
1012
- Any number of positional arguments is supported, with the same rules asin the pipe system. Multiple return values packed into a tuple are unpacked to the argument list of the next function in the chain.
1010
1013
-`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
1150
1153
1151
1154
#### ``curry`` and reduction rules
1152
1155
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:
1154
1159
1155
1160
```python
1156
1161
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
1167
1172
- If ``n < m1``, partially apply ``f`` to the given arguments, yielding a new function with smaller ``m1``, ``m2``. Then curry the result andreturn it.
1168
1173
- 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.
1169
1174
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`andreturn 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, tryfor an exact match that passes the type check. **If any such match is found**, pick that multimethod. Call it andreturn its result (as above).
1188
+
- Then, tryfor 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, tryfor 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:
0 commit comments