Well of course you can use functions and you still do when you work with coroutines. The difference is how you deal with long running or asynchronous tasks. You can either use coroutines or make them callback or message based. Personally I prefer the way coroutines can make asynchronous code look synchronous. Take the example presented in the article I linked above:
function getUserProfileLinks(userId) {
query("select * from users where id = ? limit 1", userId, function(users) {
query("select * from profiles where user_id = ? limit 1", users[0].id, function(profiles) {
query("select * from profile_links where profile_id = ?", profile.id, function(links) {
// return links??
})
})
})
// ?? can't return here since everything is async, see above
}
This looks messy and there is no way those asynchronous function calls can return a value when getUserProfileLinks() is called. The same function implemented with the use of coroutines would look like this:
function getUserProfileLinks(user_id)
local users = query("select * from users where id = ? limit 1", user_id)
local profiles = query("select * from profiles where user_id = ? limit 1", users[1])
return query("select * from profile_links where profile_id = ?", profiles[1])
end
Here the query function takes the asynchronous task of getting data from a database and makes it look synchronous. And all of a sudden the getUserProfileLinks() function can actually return links when called.