Log In  

I may have a non-standard definition of fun...

Disclaimer: I'm fully aware that this is quite likely a completely useless little utility as far as PICO-8 development is concerned. I just like writing this kind of thing just for fun. But if you do find it interesting or useful, let me know!

This is basically just a function which allows you to create curried functions—which I attempt to explain below if you're unfamiliar with them—which can be partially applied in a natural way in Lua. I'm not sure why it took me so long to write this since it's just a rip-off of my python implementation of the same thing.

Anyway, here it is on github or you can copy/paste the code from the example.

Example

Here's a simple little proto-game thing which I stuffed full of as many curried functions as I thought I could reasonably get away with. Not gonna claim that it's the best—or even good—way to organize a game but I think it does an okay job of showing how to use partial application as a sort of dependency injection/data encapsulation.

Cart #curry_demo-1 | 2022-05-03 | Code ▽ | Embed ▽ | No License
2

What is function currying?

If you've never encountered curried functions before they can seem a bit weird at first but they're actually pretty simple.

Function currying — which gets its name from mathematician Haskell Curry for whom the Haskell programming language is also named — is a transformation which turns a function which accepts multiple arguments into a series of functions which each accept only a single argument. For instance, if we have this function which accepts three arguments:

    function sum3(x, y, z)
       return x + y + z
    end

Then when we curry it, we end up with a function which takes the first argument and returns a new function. This new function accepts the second argument and returns a third function which accepts the third argument and finally returns the result. We could write that manually like so:

    function sum3_curried(x)
       return function(y)
          return function(z)
              return x + y + z
          end
       end
    end

The curry function basically does this for you so instead of having to write a bunch of nested functions manually you could just do this:

    sum3_curried = curry(3, sum3)

Or this without having to define sum3 first:

    sum3_curried = curry(
       3, function(x, y, z)
          return x + y + z
       end
    )

Which makes it easier to see what the function is doing without having to wade through multiple levels of nested functions.

And I would want to do that because…?

Curried functions can be useful in a bunch of situations but they all basically come down to one thing: partial application.

With the sum3 function you have to pass all the arguments at once or else you'll get an error. With sum3_curried you can pass one, two, or all three and you'll always get something back. In the case of one or two arguments, you'll get back a function which you can stash in a variable and use later. In other words, you can pass curried functions some of their arguments now and the rest of their arguments at some later time.

But isn't sum3_curried(1)(2) kind of ugly and annoying to write?

It sure is!

The curry function doesn't actually construct a bunch of nested functions. Instead, the function returned by curry takes a variable number of arguments and keeps track of how many it's got so far. Once it has the right number of arguments, it calls the actual function and returns the result.

These are all valid ways of calling the versions of sum3_curried created with curry:

    sum3_curried(1, 2, 3)
    sum3_curried(1)(2, 3)
    sum3_curried(1, 2)(3)
    sum3_curried(1)(2)(3)
P#111095 2022-05-03 21:16

"In other words, you can pass curried functions some of their arguments now and the rest of their arguments at some later time."

I'm gonna be honest. That sounds like a terrible idea. If you know when the other arguments are going to be passed, you can store them in a table and unpack once enough are held. If you don't know when the other arguments are going to be passed, then you'd need to either spam the type() function an awful lot or pray your code doesn't crash.

I did a google search to see where else it might be useful to curry, and found only 2 situations: 1. what amounts to the foreach() function in pico-8 and 2. parameters that usually have the same value. These are, of course, the same situation phrased differently. They also have the issue that they just need closures or other state rather than full currying like you've demonstrated here.

The thing that most concerns me about this concept, though, is that it seems like a recipe for spaghetti when lasagna would be easier to deal with and more efficient.

P#111265 2022-05-03 23:09

@kimiyoribaka That seems a little harsh. Partial function application is nice in a lot of cases, particularly when constructing callbacks, and even more particularly the special case of bind(method,obj) (as in Function.bind() from JS).

That said, I'm not sure about the practical utility of a general transformation from function to auto-currying function. It sort of makes sense from a certain theoretical perspective (everything's just lambdas until you've bound all the parameters!) ... but in practice you usually know the shape of what you're trying to achieve in advance, so you can just bind the parameters you know you need to bind.

Still a fun hack though!

P#111268 2022-05-04 01:34
1

@kimiyoribaka
Oh yeah, I don't necessarily disagree with you. There are far easier, faster, more efficient ways to accomplish the same things in Lua. I don't expect people to actually use this except maybe as a toy. I don't even expect myself to use it.

Curried functions are (more?) common in pure functional languages. I'm assuming you're not familiar with Haskell/pure functional programming so if I'm explaining things you already know, my apologies. Hopefully it's interesting and/or useful to someone. Anyway, in Haskell all functions are curried by default and the transformation actually goes the other way; if you want a function that takes all its parameters at once you have to explicitly create it using the imaginatively named uncurry function. These languages tend to have a more declarative programming style rather than the imperative, step-by-step style most of us are used to. In that context curried functions, along with function composition and all sorts of other things I won't bother getting into, make the language very compact and expressive. But of course pico-8/lua is very much not Haskell. So, as @luchak says, this is really just a fun hack. For a suitable definition of "fun."

"If you know when the other arguments are going to be passed, you can store them in a table and unpack once enough are held."

This is actually exactly what curry does. It just manages the table in the background for you so you don't have to handle it manually. Add arguments to the table by partially applying them. Unpack all the arguments and get the result when you pass the last one. Essentially, if you know when you're getting your next argument and can manually add it to a table, then you also know when you're getting your next argument and can partially apply it instead. It's really the same thing, it just looks different on the surface.

"seems like a recipe for spaghetti when lasagna would be easier to deal with and more efficient."

I disagree that it's any more (or less) prone to spaghetti code than anything else. Take these functions from the demo cart for instance.

set_attribute = curry(
   3,
   function(attribute, obj, value)
      obj[attribute] = value
   end
)

set_hp = set_attribute('hp')
set_str = set_attribute('str')
set_weap = set_attribute('weap')

If this were some other language and implemented as a module then I'd probably not expose the set_attribute function directly but only give you the set_hp, set_str and set_weap functions. If I didn't tell you they were curried all you'd know is that they're functions which take two arguments, a table and a value, and set a particular attribute (hp, str, or weap) on the given table to the given value. As far as you're concerned they're just functions like any other.

We could do it in a more familiar ways. Object oriented:

methods = {
   _set_attribute=function(self, attribute, value)
      self[attribute] = value
   end,
   set_hp=function(self, value)
      self:_set_attribute('hp', value)
   end,
   set_str=function(self, value)
      self:_set_attribute('str', value)
   end,
   set_weap=function(self, value)
      self:_set_attribute('weap', value)
   end,
}

function character()
   return setmetatable({}, {__index=methods})
end

Or just as straight functions:

function _set_attribute(obj, attribute, value)
   obj[attribute] = value
end

function set_hp(obj, value)
   _set_attribute(obj, 'hp', value)
end

function set_str(obj, value)
   _set_attribute(obj, 'str', value)
end

function set_weap(obj, value)
   _set_attribute(obj, 'weap', value)
end

The details differ but they're all conceptually identical. They each have a base function which does the actual work and isn't meant to be used on it's own and then three "public" functions which are essentially specific instances of the base function. This is a simplistic example, obviously, but you can imagine more complex functions which are set up in a similar way. The curried one is the most concise but probably also the least efficient. But no more spaghetti-like than the others, I would argue.

Anyway, my tendency towards being long winded is in full form today, so I'll shut up now. But tl;dr: I like functional programming and think currying is a neat technique. It has advantages and disadvantages. Probably wouldn't recommend anyone actually use it for their pico-8 projects.

P#111270 2022-05-04 17:30

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-03-29 08:34:30 | 0.030s | Q:22