Let's have some fun with TypeScript syntax. Thanks to the fat arrow interface, you can easily match (with some caveats) one of the best features of Haskell called currying. Currying is the ability to partially apply a function. Let's build a small function that concats strings first, we'll see a situation on which this technique can be useful.
An obvious reference implementation for our cat function could be:
function cat(a: string, b: string): string {
return a + ' ' + b
}
But this function can't be applied partially if we call it with just the first parameter, we'll have a funny result (thanks JavaScript):
cat("hello")
=> "hello undefined"
In Haskell all functions are curried by default, so the obvious implementation will behave very differently:
cat :: String -> String -> String
cat a b = a ++ " " ++ b
-- Calling it with one parameter returns a
-- partially applied function
let catHello = cat "hello"
-- we can apply the other parameter later
catHello "World"
=> "Hello World"
The fat arrow syntax and TypeScript type annotations allow us to implement currying in a non really that surprisingly similar way. The trick is very easy, we just need to nest single-parameter functions that return a function for the next parameter, writing the implementation in the last function:
let cat: (a: string) => (b: string) => string =
(a) => (b) => a + ' ' + b
// To apply the whole function, you'd need to pass
// the parameters separately:
console.log(cat('Hello')('World'))
=> "Hello World"
let catHello = cat('Hello')
console.log(catHello('World'))
=> "Hello World"
If you're wondering how this can possibly work: A function defined in another function can access the scope of the previous function via closures, so inner functions can see the parameter values of previous ones. You can learn more about this useful technique here.
Notice that if we could remove the parentheses, the implementation would be almost the same as Haskell's!
Why partial application is interesting?
It is a nice way to inject context or dependencies into functions without wrapping them in classes, and they're an awesome tool combined with iterators or to build callbacks:
let dbWrite: (dbConnection: DBConnection) => (data: string) => bool =
(dbConnection) => (data) => ...
let productionDBWrite = dbWrite(productionDBConnection)
request.data.map(productionDBWrite)
BUT... you probably want to use bind
instead!
As I said at the beginning of the article, I wrote it just for fun, I love to find how languages like Haskell inspire features and new ways of thinking in other more trendy languages.
In JavaScript you can use Function.prototype.bind()
to achieve similar results:
function cat(a: string, b: string): string {
return a + ' ' + b
}
let catHello = cat.bind(null, "Hello")
// Take into account that the first parameter defines
// where "this" points to in the bound function. It's
// important to remember this if you're building a
// bound function from a class method. As we don't
// reference `this` in our function, it's fine to just
// pass `null` here.
console.log(catHello('World'))
=> "Hello World"
We use this technique extensively in Booster Framework to reduce code complexity. Feel free to join our community and practice some hardcore TypeScript techniques there, contributions from any skill level are highly appreciated, there's nothing like learning together!
Top comments (2)
First time I've seen that bind method. Quite enlightning.
In the other hand, you might get used to infinite recursion
That's basically one stateful way of implementing function composition, which in Haskell is trivial with the
.
operator:In TypeScript, there are some libraries that implement function composition like
fp-ts
, but you can create your own pretty easily using Array.prototype.reduceRight:In this
compose
implementation, we're using currying again to build a function that expects the parameter of the final composition to be used as the initial value for thereduceRight
function.reduceRight
iterates our functions from right to left, resolving each function and passing its return value as an argument to the previous one.