You may encounter curry in everyday code without knowing it. Here is a bit of my reflections on curry and how to apply it in Javascript and Swift.
Taking one parameter in Haskell
In Haskell, all function officially takes only 1 parameter. Function with many parameters are just curried, which means they will be partially applied. Calling sum 1 just returns a function with 1 parameter, then 2is passed into this function. The following 2 function calls are the same.
ghci> sum 1 2
3
ghci> (max 1) 2
3
I tend to think of curried function or partially applied function as something that carry dependencies at each application step. Each curried function can be assigned to variable or pass around, or as returned value.
Curry in Swift for predicate
When I was trying to make my own Signal library, I have
and Event
filter
Then there should be a filter for Signal. The idea of filter is that we should update signal if the Event is Next with right filtered value
public func filter(f: T -> Bool) -> Signal<T>{
let signal = Signal<T>()
subscribe { result in
switch(result) {
case let .Success(value):
if f(value) {
signal.update(result)
}
case let .Error(error): signal.update(.Error(error))
}
}
return signal
}
2 parameters
But having Event as another monad, I think it should be more encapsulated if that switching logic gets moved into the Event. Here the filter takes 2 params
Event.swift
func filter(f: T -> Bool, callback: (Event<T> -> Void)) {
switch self {
case let .Next(value) where f(value):
callback(self)
case .Failed:
callback(self)
default:
break
}
}
Signal.swift
public func filter(f: T -> Bool) -> Signal<T> {
let signal = Signal<T>()
subscribe { event in
event.filter(f, callback: signal.update)
}
return signal
}
Currying
With currying, we can make filter a more abstract function, and defer the decision to pass the callback param. It is a little carried away but I find it helpful this way
Now filter accepts 1 param, and it returns a function that takes callback as its param
Event.swift
func filter(f: T -> Bool) -> ((Event<T> -> Void) -> Void) {
return { g in
switch self {
case let .Next(value) where f(value):
g(self)
case .Failed:
g(self)
default:
break
}
}
}
Signal.swift
public func filter(f: T -> Bool) -> Signal<T> {
let signal = Signal<T>()
subscribe { event in
event.filter(f)(signal.update)
}
return signal
}
Curry syntax in Swift 2 and above
Swift 2 supports curry syntax function
func sum(a: Int)(b: Int) -> Int {
return a + b
}
let sumWith5 = sum(5)
let result = sumWith5(b: 10)
Unfortunately, the syntactic sugar for declaring curry has been dropped since Swift 3. You may want to find out in Bidding farewell to currying. But itβs not a big deal as we can easily create curry function. It is just a function that returns another function.
Using curry for partial application in UIKit
I used this curry technique in my Xkcd app. See MainController.swift. MainController is vanilla UITabBarController with ComicsController and FavoriteController , all embedded in UINavigationViewController .
The feature is that when a comic is selected, a comic detail screen should be pushed on top of the navigation stack. For example in ComicsController
/// Called when a comic is selected
var selectComic: ((Comic) -> Void)?
All ComicsController needs to know is to call that selectComic closure with the chosen Comic, and someone should know how to handle that selection. Back to the handleFlow function inside MainController.
private func handleFlow() {
typealias Function = (UINavigationController) -> (Comic) -> Void
let selectComic: Function = { [weak self] navigationController in
return { (comic: Comic) in
guard let self = self else {
return
}
let detailController = self.makeDetail(comic: comic)
navigationController.pushViewController(detailController, animated: true)
}
}
comicsController.selectComic = selectComic(comicNavigationController)
favoriteController.selectComic = selectComic(favoriteNavigationController)
}
I declared Function as typealias to explicitly state the curry function that we are going to build
typealias Function = (UINavigationController) -> (Comic) -> Void
We build selectComic as curried function, that takes UINavigationViewController and returns a function that takes Comic and returns Void . This way when we partially apply selectComic with the a navigationController , we get another function that has navigationController as dependency, and ready to be assigned to selectComic property in comicsController .
Curry promised function in Javascript
I like to work with Promise and async/await in Javascript. It allows chainable style and easy to reason about. So when working with callbacks in Javascript, for example callback from native modules in React Native, I tend to convert them into Promise.
For example when working with HealthKit, we need to expose a native modules around it
// [@flow](http://twitter.com/flow)
import { NativeModules } from 'react-native'
type HealthManagerType = {
checkAuthorisation: ((string) => void)) => void,
authorise: ((boolean) => void)) => void,
readWorkout: (Date, Date, () => void)) => void,
readDailySummary: (Date, Date, () => void)) => void,
readMeasurement: (Date, Date, () => void)) => void
}
const HealthManager: HealthManagerType = NativeModules.HealthManager
export default HealthManager
We can build a toPromise function that can convert a function with callback into Promise
// [@flow](http://twitter.com/flow)
const toPromise = (f: (any) => void) => {
return new Promise<any>((resolve, reject) => {
try {
f((result) => {
resolve(result)
})
} catch (e) {
reject(e)
}
})
}
export default toPromise
However, as you can see in the signature, it only works with a callback of type (any) => void In other words, this callback must have exactly 1 parameter, because a Promise can either returns a value or throws an error.
To remedy this, we can build a curry function that can turns function with either 1, 2, 3 parameters into curried function. Thanks to the dynamic nature of Javascript, we have
// [@flow](http://twitter.com/flow)
function curry0(f: () => void) {
return f()
}
function curry1(f: (any) => void) {
return (p1: any) => {
return f(p1)
}
}
function curry2(f: (any, any) => void) {
return (p1: any) => {
return (p2: any) => {
return f(p1, p2)
}
}
}
function curry3(f: (any, any, any) => void) {
return (p1: any) => {
return (p2: any) => {
return (p3: any) => {
return f(p1, p2, p3)
}
}
}
}
export default {
curry0,
curry1,
curry2,
curry3
}
So with a function that have 3 parameters, we can use curry3 to partially apply the first 2 parameters. Then we have a function that accepts just a callback, and this is turned into Promise via toPromise
const readWorkout = curry.curry3(HealthManager.readWorkout)(DateUtils.startDate))(DateUtils.endDate))
const workouts = await toPromise(readWorkout)
Where to go from here
Here are some of my favorite posts to read more about curry
Currying in JavaScript: I like how he uses memory and slice to gradually build more generic curry function
Original post https://medium.com/fantageek/curry-in-swift-and-javascript-bcd1245b30d3
Top comments (0)