Despite the popularity of currying and the rise of functional programming (and of TypeScript), it is still a hassle today to make use of curry and have . Even famous libraries like Ramda do not provide generic types for their curry implementations (but we will). proper type checks However, you need no functional programming background to follow this guide. The guide is about currying but it is only a topic of my choice to teach you advanced TypeScript techniques. You just need to have practised a bit with TypeScript’s primitive types. And by the end of this walk-through, you will be a real TS wizard 🧙. If you’re a functional programmer, you are probably already using currying to create powerful compositions and partial applications… And if you are a bit behind, it’s time to take the leap into functional programming, start shifting away from the imperative paradigm and solve problems faster, with ease, and promote reusability within your codebase. At the end of this guide, you will know how to create like: powerful types In fact, Ramda does have some kind of mediocre types for curry. These types are not generic, , limiting us to a certain amount of parameters. As of version 0.26.x, it only follows a and does not allow us to use its famous feature very easily with TypeScript. Why? It’s hard, but we agree that we had enough and we’re going to fix this! hard-coded maximum of 6 arguments placeholder What is currying? But before we start, let’s make sure you have a very basic understanding of what currying is. Currying is the process of transforming a function that takes multiple arguments into a series of functions that take one argument at a time. Well that’s the theory. I prefer examples much more than words, so let’s create a function that takes two numbers and that returns the result of their addition: The curried version of would be: simpleAdd In this guide, I will first explain how to create TypeScript types that work with a standard curry implementation. Then, we will evolve them into more that can allow curried functions to take 0 or more arguments. advanced types And finally, we will be able to use “gaps” that abstract the fact that we are not capable or willing to provide an argument at a certain moment. : We will create types for “classic curry” & “advanced curry” (Ramda). TL;DR Tuple types Before we start learning the most advanced TypeScript techniques, I just want to make sure that you know . Tuple types allow you to express an array where the type of a fixed number of elements is known. Let’s see an example: tuples They can be used to enforce the kind of values inside a fixed size array: And can also be used in combination of rest parameters (or destructuring): But before starting to build our awesome curry types, we’re going to do a bit of a warmup. We are going to create the first tools that we need to build one of the most basic curry types. Let’s go ahead. Maybe you could guess… We are going to work with tuple types a lot. We’ll use them as soon as we extracted the parameters from the “original” curried function. So for the purpose of an example, let’s create a basic function: We extracted the parameter types from thanks to the magic of Parameters. But it’s not so magical when you recode it: fn00 Let’s test it: Good, it works just as does. Don’t be scared of , it is one of the most powerful keywords for building types. I will explain it in more detail right after we practiced some more: Parameters infer Head Earlier, we learnt that a “classic curried” function takes one argument at a time. And we also saw that we can extract the parameter types in the form of a tuple type, very convenient. So takes a tuple type and returns the that it contains. This way, we’ll be able to know what argument type has to be taken at a time. Head T first type Let’s test it: Tail A “classic curried” function consumes arguments . This means that when we consumed the , we somehow need to move on to the that hasn’t been consumed yet. This is the purpose of , it conveniently removes the first entry that a tuple might contain. one by one Head<Params<F>> next parameter Tail As of TypeScript 3.4, we cannot “simply” remove the first entry of a tuple. So, we are going to work around this problem by using one very : valid trick Using , we were able to tell TypeScript to infer the tuple that we wanted. If you do not understand it yet, it is not a problem, this is just a warmup, remember? function types Let’s test it: HasTail A curried function will return a function until all of it’s parameters have been . This condition is reached when we called enough times that there is no tail left, nothing’s left to consume: consumed Tail Let’s test it: Important keywords You have encountered 3 important keywords: , and . They can be pretty confusing for beginners, so these are the ideas they convey: type extends infer : To keep it simple, you are allowed to think of it as if it was our dear old JavaScript’s . When you do so, you can see an statement as a , and then it becomes much simpler to understand. In this case, extends is referred to as a . - extends === extends simple ternary conditional type : I like to think of a type as if it was a , but for types. It has an input, which are types (called ) and has an output. The output depends on the “logic” of a type, and is that block of logic, similar to an clause (or ternary). - type function generics extends if : It is the magnifying glass of TypeScript, a beautiful inspecting tool that can that are trapped inside different kinds of structures! - infer extract types I think that you understand both & well and this is why we are going to practice a bit more with . We’re going to extract types that are contained inside of different generic types. This is how you do it: extends type infer Extract a property’s type from an object Let’s test it: Extract inner types from function types Let’s test it: Extract generic types from a class or an interface Let’s test it: Extract types from an array Let’s test it: Extract types from a tuple Let’s test it: We tried to infer the type of the into a type but it did not work as expected. It is because TypeScript of a feature that would allow us to a tuple into another one. There is an active proposal that tackles these issues and you can expect improved manipulation for tuples in the future. This is why is constructed the way it is. rest of the tuple B lacks deconstruct Tail is very powerful and it will be your for type manipulation. infer main tool Curry V0 The warm-up 🔥 is over, and you have the knowledge to build a “classic curry”. But before we start, let’s summarize (again) what it must be able to do: Our first curry type must take a tuple of and a type . It is a function type that with the : parameters P return R recursive varies length of P If reports , it means that the parameters were and that it is time to the return type from the original function. Otherwise, there’s parameters , and we within our type. Recurse? Yes, describes a function that has a return type of as long as there is a ( ). HasTail false all consumed return R left to consume recurse CurryV0 CurryV0 Tail HasTail<P> extends true This is as simple as it is. Here is the proof, without any implementation: But let’s rather visualize the recursion that happened above, step by step: And of course, type hints work for an amount of parameters 🎉: unlimited Curry V1 Nice, but we forgot to handle the scenario where we pass a : rest parameter We tried to use a rest parameter, but it won’t work because we actually expected a parameter/argument that we earlier called . So we want to take at least one argument and we want to receive any extra (optional) arguments inside a rest parameter called . Let’s enable taking rest parameters by upgrading it with & : single arg0 arg0 rest Tail Partial Let’s test it: But we made a horrible mistake: the arguments are consumed very badly. According to what we wrote, this will not produce a single TS error: In fact there is a big because we said that we would force taking a single . Somehow, we are going to need to of the arguments that are at a time. So, we will first get rid of and start tracking consumed parameters: design problem arg0 keep track consumed arg0 There, we made use of a generic called that is going to any taken arguments. But now, it is completely broken, there is no more type checks because we said that we wanted to track kind of parameters (the constraint). But not only that, is completely useless because it only worked well when we took one argument at a time. constrained T track any[] Tail There is only one solution: 🔧. some more tools Recursive types The following tools are going to be used to determine the next parameters to be consumed. How? By tracking the consumed parameters with we should be able to . T guess what’s left Fasten your seat belt! You are about to learn another powerful technique 🚀: Last Take your time to try to understand this complex yet very short type. This example takes a tuple as a parameter and it extracts its last entry out: Let’s test it: This example demonstrates the power of conditional types when used as an indexed type’s . A what? A conditional type that accesses a type’s inner types in a command line fashion. For a more visual explanation: accessor This technique is an approach and a safe way to do like we just did. But it is not only limited to recursion, it is a nice and a visual way to complex . ideal recursion organise conditional types Basic tools #1 Where were we? We said that we needed tools in order to . It means that we need to know what parameter types we can take, which ones have been consumed and which ones are the next to come. Let’s get started: track arguments Length To do the analysis mentioned above, we will need to over tuples. As of TypeScript 3.4.x, there is no such iteration protocol that could allow us to iterate freely (like a ). Mapped types can map from a type to another, but they are too limiting for what we want to do. So, ideally, we would like to be able to manipulate some sort of : iterate for counter Let’s test it: By a tuple up with , we created something that could be similar to a variable that can be . However, is just about giving the size of a tuple, so it also works with any other kind of tuple: topping any incremented Length Prepend It adds a type at the of a tuple by using our first TS trick: E top T Let’s test it: In ’s examples, we manually increased a counter. So is the ideal candidate to be the base of a . Let’s see how it would work: Length Prepend counter Drop It takes a tuple and drops the first entries. To do so we are going to use the same techniques we used in and our brand new counter type: T N Last Let’s test it: What happened? The type will recurse until the value of that we passed. In other words, the type of index is chosen by the conditional accessor until that condition is met. And we used so that we can a counter like we would do in a loop. Thus, is used as a recursion , and it is a way to freely iterate with TS. Drop Length<I> matches N 0 Prepend<any, I> increase Length<I> counter Curry V3 It’s been a long and tough road to get here, well done! There’s a reward for you 🥇. Now, let’s say that we tracked that 2 parameters were consumed by our curry: Because we know the amount of consumed parameters, we can guess the ones that are still left to be consumed. Thanks to the help of , we can do this: Drop It looks like and are precious tools. So let’s revamp our previous version of curry, the one that had a broken : Length Drop Tail What did we do here? First, means that we remove consumed parameters out. Then, if the length of is not equal to , our curry type has to with the dropped parameters until… Finally, when of the parameters were , the of the dropped parameters is equal to , and the return type is . Drop<Length<T>, P> Drop<Length<T>, P> 0 continue recursing all consumed Length 0 R Curry V4 But we’ve got another error above: TS complains that our is not of type . Sometimes, TS will that a type is not the one you expected, but you know it is! So let’s add another tool to the collection: Drop any[] complain Cast It requires TS to a type against a type , and type will only be enforced if it fails. This way, we’re able to stop TS’s complaints: re-check X Y Y Let’s test it: And this is our previous curry, but without any complaint this time: Remember earlier, when we lost the type checks because we started tracking consumed parameters with ? Well it has been fixed by casting to . We added a constraint with ! T extends any[] T Partial<P> Cast<T,Partial<P>> Let’s test it: Curry V5 Maybe you thought that we were able to take rest parameters. Well, I am very sorry to inform you that we are not there yet. This is the reason why: Because rest parameters can be , TS’s best guess is that the length of our tuple is a , it’s kind of clever! So, we make use of while dealing with rest parameters. Don’t be sad, it’s not so bad: unlimited number cannot Length When all the non-rest parameters are consumed, can only match . Thanks to this, we used as a condition to the recursion. Drop<Length<T>, P> […any[]] [any,…any[]] end Let’s test it: Everything works like a charm 🌹. You just got yourself a smart, , . You will be able play with it very soon… But before you do so, what if I told you that our type can get even more awesome? generic variadic curry type Placeholders How awesome? We are going give our type the ability to partial application of , on any position. According to Ramda’s documentation, we can do so by using a called . It states that for any curried function , these calls are equivalent: understand any combination of arguments placeholder _ f A placeholder or “gap” is an object that abstracts the fact that we are not capable or willing to provide an argument at a certain moment. Let’s start by defining what a placeholder is. We can directly grab the one from Ramda: Earlier, we have learnt how to do our first type iterations by increasing a tuple’s length. In fact, it is a bit confusing to use and on our counter type. And to make it , we will refer to that counter as an from now on. Here’s some new aliases just for this purpose: Length Prepend clearer iterator Pos (Position) Use it to query the position of an iterator: Next (+1) It brings the position of an iterator up: Prev (-1) It brings the position of an iterator down: Let’s test them: Iterator It creates an iterator (our counter type) at a position defined by and is able to start off from another iterator’s position by using : Index From Let’s test it: Basic tools #2 Good, so what do we do next? We need to whenever a placeholder is passed as an argument. From there, we will be able to tell if a parameter has been “skipped” or “postponed”. Here’s some more tools for this purpose: analyze Reverse Believe it or not, we still lack a few basic tools. is going to give us the freedom that we need. It takes a tuple and turns it the other way around into a tuple , thanks to our brand new iteration types: Reverse T R Let’s test it: Concat And from , was born. It simply takes a tuple and merges it with another tuple . It’s kind of what we did in : Reverse Concat T1 T2 test59 Let’s test it: Append Enabled by , can add a type at the end of a tuple : Concat Append E T Let’s test it: Gap analysis We now have enough tools to perform . But it’s been a while since we discussed this “gap” feature, how does it work again? When a gap is specified as an argument, its matching parameter is to the next step (to be taken). So let’s define types that understand gaps: complex type checks carried over GapOf It checks for a placeholder in a tuple at the position described by an iterator . If it is found, the matching type is at the same position in and carried over (saved) for the next step through : T1 I collected T2 TN Let’s test it: GapsOf Don’t be impressed by this one. It calls over & and stores the results in TN. And when it’s done, it concats the results from to the parameter types that are left to be taken (for the next function call): Gap T1 T2 TN Let’s test it: Gaps This last piece of the puzzle is to be applied to the tracked parameters . We will make use of to explain that is is possible replace any argument with a : T mapped types placeholder A mapped type allows one to iterate and of another type. In this case, we altered so that each entry can be of the placeholder type. And thanks to , we explained that each entry of is optional. It means that we no longer have the need to use on the tracked parameters. alter properties T ? T Partial Let’s test it: Ugh, we never said that we could take ! We just wanted to be able to omit a part of . It is a of using the operator. But it is not that bad, we can fix this by re-mapping with : undefined T side effect ? NonNullable So let’s put the two together and get what we wanted: Let’s test it: Curry V6 We’ve built the last tools we will ever need for our curry type. It is now time to put the last pieces together. Just to remind you, is our new replacement for , and will replace our previous : Gaps Partial GapsOf Drop Let’s test it: In order to make sure that everything works as intended, I am going to force the values that are to be taken by the curried example function: There is just a little problem: it seems like we’re a bit ahead of Ramda! Our type can understand very complex placeholder usages. In other words, Ramda’s placeholders just when they’re combined with rest parameters: don’t work However, even if this looks perfectly correct, it will result in a complete crash. This happens because the implementation of Ramda’s curry does not deal well with combinations of . This is why I opened a ticket with Ramda on Github, in the hope that the types we’ve just created could one day work in harmony with the library. placeholders and rest parameters Curry This is very cute, but we have one last problem to solve: . I don’t know about you, but I use parameter hints a lot. It is very useful to know the names of the parameters that you’re dealing with. The version above does not allow for these kind of hints. Here is the fix: parameter hints I admit, it’s completely awful! However, we got hints for . What did we do here? We just replaced the parameter types & that used to stand for parameter types and return type, respectively. And instead, we used the from which we extracted the equivalent of with and with . Thus, TypeScript is able to conserve the name of the parameters, even after currying: Visual Studio Code P R function type F P Parameters<F> R ReturnType<F> There’s just one thing: when using gaps, we’ll lose the name of a parameter. A word for IntelliJ users only: You won’t be able to benefit from proper hints. I recommend that you switch to Visual Studio Code as soon as possible. And it is community-driven, free, much (much) faster, and supports key bindings for IntelliJ users. :) Last Words I would like to inform you that you just read the tutorial of the . It is a collection of types that brings higher type safety to TypeScript. It all started from this article, and this is a few things that it could do for you: ts-toolbelt That’s it. I know that it’s a lot to digest at once, so that’s why I also released a of this article. You can clone it, test it, and change it with TypeScript 3.3.x and above. Keep it close to you and learn from it until you become more comfortable with the different techniques 📖. developer version High-five 👏 if you enjoyed this guide, and stay tuned for my next article! EDIT: It’s available for Ramda 0.26.1 . And if you have any questions or remarks, you are more than welcome to leave a comment. Thanks for reading