7
\$\begingroup\$

I have been writing Swift code for a while and have read a lot of tutorials about how functional paradigms can be applied to Swift.

I defined a simple operator:

infix operator >>> {associativity left}
func >>><A,B>(a: A?, f: A -> B?) -> B? {
    if let a = a {
        return f(a)
    } else {
        return .None
    }
}

Now for instance I am gonna populate a UITableViewCell instance from Core Data entity:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    func getWalks(dog: Dog) -> NSOrderedSet? {
        return dog.walks
    }

    func getWalkAtIndex(walks: NSOrderedSet) -> Walk? {
        return walks[indexPath.row] as? Walk
    }

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell

    func setUpCell(walk: Walk) {
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateStyle = .ShortStyle
        dateFormatter.timeStyle = .MediumStyle

        cell.textLabel.text = dateFormatter.stringFromDate(walk.date)
    }

    self.currentDog >>> getWalks >>> getWalkAtIndex >>> setUpCell

    return cell
}

I am creating a function for every step, though right now I am not good at separating steps but I kind of like this style of coding. What is your opinion about this?

\$\endgroup\$
0
3
\$\begingroup\$

This coding style is confusing.

Let's unravel the line of code in question:

self.currentDog >>> getWalks >>> getWalkAtIndex >>> setUpCell

What's happening here? To make sense of this, we essentially have to read this backwards.

What are we doing? We're setting up a cell.

With what information are we setting up the cell? The walk at index. (What index? Well, the function has a bad name...)

What collection are we looking through for this index? The one returned by "get walks".

And what does that return? self.currentDog.walks


If we were to code this more normally, it'd look more like this:

let cell = tableView.dequeueReusableCellWithIdentifier("Cell", 
    forIndexPath: indexPath) as UITableViewCell

let currentWalk = self.currentDog.walks[indexPath.row];

let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = .ShortStyle
dateFormatter.timeStyle = .MediumStyle

cell.textLabel.text = dateFormatter.stringFromDate(currentWalk.date)

This is fewer lines of code.

This is going to be less confusing to your average Swift programmer. Swift programmers aren't very likely to be functional programming types.

Ultimately, the biggest problem here is that the operator you've created relies on the left hand argument being the argument for a function on the right hand side. Which means in order to use this, we have to invent lots of very specific functions that are very specific toward the objects we have.

For example, the getWalks function is only usable on Dog objects and it simply returns the walk array.

Why would I ever want to do dog >>> getWalks when I can just do dog.walks?

And truly, should this operator be different somehow? The dog isn't getting the walks. We're applying the getWalks function to the dog. dog <<< getWalks makes more sense. The arrows point to the object we're getting walks of. However, I actually like getWalks >>> dog better. This reads better if we say "of" in place of the arrows.

"Get walks of dog"

And it looks more like a function we'd write.

getWalks(dog)

And that feels more like functional programming.

But the problem is when we start inventing functions that aren't reusable and end up extending the lines of code we write just for the sake of using an operator. Moreover, these are functions that don't even take in the necessary number of arguments.

Isn't indexPath.row a value that should be passed into getWalkAtIndex? The easiest, clearest thing to do would just treat the returned walks array as what it is... an array... and get the object at index the same way you would anyway... but if you insist on a function, then the function should be more like this:

getWalk(walks: NSOrderedSet index atIndex:NSInteger) -> Walk? {
    return walks[index] as Walk?
}

And we should call it as such:

getWalk(walks atIndex:indexPath.row)

We shouldn't require an NSIndexPath when we're only going to use the .row property of it. And we really shouldn't be reaching outside of the function at what is effectively a global variable as far as this function is concerned.

Some of the same can be said for the setUpCell function. Although truly, this should be a function. We don't need a function to set a cell's text label's text property.

The function should be to format the date string. And it should simply take an NSDate object and return a string.

So we'll have:

cell.textLabel.text = formatDate(walk.date)

At the end of the day though, Swift is an object oriented language. If you're writing Swift, you're using all of the Apple libraries, frameworks, etc. And that means you're using a lot of stuff that was designed for OOP. And unless you intend to write a function sister to match your pattern for all of these existing methods for all these classes, then any code you write using this operator is going to be a hodge-podged mix of styles that will be confusing to people expecting OOP.

\$\endgroup\$
1
\$\begingroup\$

Given that Swift has first class functions (aka 'clousres') you can adopt a more functional style by using composition to create new functions from other functions. That is, you've defined getWalks() and getWalkAtIndex(), you could define getDogWalkAtIndex() as getWalkAtIndex(getWalks(dog)). Here is a compose() 'top-level' function:

func compose<T, U, V> (f: (U) -> V, g: (T) -> U) -> (T) -> V {
  return { (x:T) in f (g (x)) }
}

which you could use like:

var getDogWalkAtIndex = compose (getWalkAtIndex, getWalks)

getDogWalkAtIndex (...)

You can compose arbitrary chains of functions. In your case

var doIt = compose (setupCell, compose (getWalkAtIndex, getWalks))

which looks like a 'normal' calling sequence.

In your >>> operator, you've accounted for optionals. The compose() function I've provided doesn't, but could be, perhaps as simply as:

func compose<T, U, V> (f: (U) -> V, g: (T) -> U) -> (T) -> V {
  if let gx = g(x) {
     return f(gx)
  }
  return nil
}

(but this is less general as some functions might actually want to see optional inputs - the above doesn't allow that, but neither did yours)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.