Ashbad's Using Axe to its full functional form Ed.2

Submitted By: Date: July 28, 2011, 10:12:50 pm Views: 1362
Summary: A kickass tutorial by badass programmer Ashbad that covers concepts concerning functional programming in Axe. Included subjects are Functions, Returning Values, Lambda expressions, Currying and Closures, and more.

Like this article?  Feel free to post comments or express your appreciation with thumbs-ups here: http://www.omnimaga.org/index.php?topic=9523.0

I - Introduction

Hello, Ashbad again.  I'm back to rewrite my original tutorial to using functions and other entities found in functional programming in Axe.  Disregard the old version now; this one teaches you how to use the new functional features of Axe 1.0.0 and higher, whereas the old one was modeled to hackishly attempt some simple functional workings in Beta versions of Axe, 0.5.3 and lower.

This version is completely rewritten and uses now text from the first tutorial, so that I can start fresh, as all the actual code content shown must be fresh as well.  This time I will try to explain things a little better, and even use examples comparing Axe code to Ruby/Python code so that people who have used higher level functional ideals in computer languages before can simply use this as a reference to see the Axe equivalent for something.

With that, good luck with the reading, and feel free to post questions below, since this is a lot of material to cover and I'm covering it slightly fast, so don't feel awkward in doing so.

Note: the first few sections are really basic to intermediate-to-advanced Axe programmers, so feel free to skip them if you already know what subroutines are and how to return values.


II - Simple Functions

What are functions?  Functions, called subroutines in Axe, are essentially like the functions seen in math classes like Algebra I and II.  Functions receive a list of variables, numbers, or expressions, called arguments, in a list, and then use the input values to determine an outcome value.  More simply for those who said "LOL MAN, ENGRISH PLZ", I'll provide a code example, since it is important to understand how subroutines work, otherwise you'll be all LOLWAT later on in this tutorial.  Here is an example of a subroutine's code body, the place where the output value of the function is determined:

Code: [Select]
Lbl CAT
  r1+r2 -> A
Return


This is a standard subroutine, which starts with a name, in this CAT, some code inside, and a Return which ends the declaration.  The values r1 - r6 are variables that represent the passed in arguments.  These are argument variables, which means their values are local to the subroutine body, which in English means that they only have any significance when they're used inside of the routine's code body.  Here is how we can call routine CAT:

Code: [Select]
CAT(5,6)

When we run this, A will be equal to 11.  Why is this?  Because the first argument, 5, is stored into r1, same as 6 and r2; in the code body of this routine we called, we stored the value of argument variable 1 plus the value of argument variable 6 and stored it into A. If we did this all inline (all of the code was done without routines, and we added in the formula directly into the code instead), it would look like this:

Code: [Select]
5 + 6 -> A

These subroutines are the basis for all Axe functional programming, and all functional programming in general.  By now you should understand how they basically work, so it's time to start stepping it up a notch in the learning curve, and talk about returned values.


III - Returning Values

Sometimes, you'll want to be able to use a subroutine as part of an expression, but don't want to have to store the answer to a variable every time you want to use the output value of the routine.  In the last section, we used a really simple subroutine, CAT, to add two arguments together and output the value into A.  What if you are already using A and the rest of the variables, or want to store the output to an arbitrary variable instead of having to write different subroutines each time?

This is where Returned Values come into play.  But before I go directly into this concept, I'll explain a natural phenomenon of Axe.  In Axe, each expression you put in your code will eventually have a final value, such as 5+6*3, which will evaluate to 33.  Where is this expression value stored?  The latest value that has been a result of an expression is stored in an Assembly register called HL, a 16 bit general purpose (though commonly used with pointers in Assembly) register that Axe uses as a placeholder for values though expressions.  For BASIC programmers, here is a quote that will help you understand the purpose of HL:

Quote from: runer
HL is like the Ans of Axe.


Like you can use Ans in BASIC, you can use HL in Axe, though not directly as a variable.  Because Axe works this way, Returning values from expressions is entirely possible.  Here is a change to our original routine, CAT:

Code: [Select]
Lbl CAT
  r1+r2
Return


Notice here the only difference is that we took away the ->A part.  Why did we do this?  Instead of storing the result of r1+r2 to A, we pass it only to HL instead.  Let's see what we can now do differently with this, now that A is no longer destroyed by the routine:

Code: [Select]
CAT(5,6)+A -> B

With the old example, the inline version would look like this:

Code: [Select]
5 + 6 -> A + A ->B

The result is 22, since we destroy the original value of A, and when we add it again we're just adding it's new value to itself, and storing the final result into B.  With out new code body for CAT, here's what it looks like inline now:

Code: [Select]
5 + 6 + A -> B

Whatever was stored in A before is now left intact; therefore, B will now hold 11 plus whatever was originally in A.

Here is a computer language equivalent of what I just showed above:

Code: [Select]
def CAT(arg1, arg2)
  return arg1+arg2
end

B = A + CAT(5, 6)


Keep in mind, HL is constantly being changed every expression, so only the very last expression of the code body for a subroutine trying to act like a function (a subroutine which simply is a stand in for a value, and it's output is determined completely by the input arguments) will be passed as the output of the function.

This is a decent amount of information for people new to subroutines and functions to process in one day, and if you genuinely learned all of what I just taught for the first time ever from reading this just now, practice with what I told you and make sure you have the concepts down perfectly before continuing on.  I suggest you stop reading for now, let it soak in for a little while (a day, perhaps) before continuing on, the following will just make you confused.  For those who totally understand this already, read ahead.


IV - Fun with Addresses

The only thing we've talked about concerning how to call functions are labels.  Up until axe 1.0.0, this was the only way we could call them.  We now have a new way to call themes well: by address :).  When we call a function by address, we can do a whole new slew of tricks previously impossible before the addition of this wonderful feature.  Here is the syntax for calling a function this way:

Code: [Select]
(ADDRESS)(ARGUMENTS)

Which is actually pretty simple design.  How do we get the address of a label, so we can maybe do an offset to the beginning of the function?  Putting the list token (L; note in my examples I use a big L with a small V in front of it to denote the little L, heads up) in front of the name in an expression.  Here's a new way to call CAT, but in this form:

Code: [Select]
(LvCAT)(5,6)
.Same as:
CAT(5,6)


Overall, It's a decently simple concept to work with.  We build upon this later when we work with closures, lambdas, etc.  Here's another few tricks you can do with getting the addresses of labels:

Simple SMC with copying one function to another (the labels with the E at the end mean they were put right after the return statement in the code body of the corresponding function):

Code: [Select]
Copy(LvCAT,LvDOG,LvCATE-LvCAT)

Finding the size of a routine:

Code: [Select]
Disp LvCATE-LvCAT >Dec

In computer languages you usually don't get this type of freedom, not even in lower level functional languages (with a few exceptions), so no computer language references here.

If you understand this and think it's an easy concept, good.  It has to be if you're going to go further in this tutorial; it is absolutely fundamental to everything else coming up.  Hence, I will now introduce you to our new friends, Lambdas.


V - Lambdas

Now, here is the part that everyone gets confused with.  What is a lambda?  A Greek character?  A function that is very special in use?  An interesting feature of functional programming?  Something you'll never use?  It's the first three ones ;-).  It is indeed a Greek character, which in token form in an Axe program, takes the place of log(.  It is indeed a function, and it is more than just a simple one at that, so yes, special.  And, it is an interesting feature of functional programming.  I don't find it a useless feature though, and you'll see why I think that.

A Lambda, or anonymous function, is a function that has no label name whatsoever.  The only way you can access or call a lambda is through it's address, using techniques similar to those we just learned above.  A lambda can also be used inline with an expression, and called in the same line it was initialized (though, this is optional).  Lambdas are useful, especially when we go into higher-level types of functions like maps, folds, and filters.

Since that might confuse some people, here is our first example of a lambda:

Code: [Select]
λ(r1+r2)(5,6)

what does this do?  It looks confusing, but it actually isn't.  The first set of parenthesis after the lambda symbol is actually the code block for the anonymous function.  Code blocks in lambdas do not end with a return statement; it actually throws an error.  Rather, their last expression of code is automatically returned into HL.  The second set of parenthesis is the argument list, which is straightforward now that you know what that is.  This line of code simply returns 11, as it initializes the lambda expression and calls it immediately.

Here is how to store the address of a lambda for later use:

Code: [Select]
λ(r1+r2) -> L

When we don't add the parenthesis after the lambda expression, we simply just return the address of the lambda into HL, which we then stored here into 16 bit variable L.  To call this lambda again:

Code: [Select]
(L)(5,6)

Lambdas are very helpful for quick evaluation of functions, and can be easily stored into a list and have the correct one called for evaluation of an expression based on the current program conditions.  They're also useful inside of variable-passed loops and inside other functions and even other lambdas, which I will talk about soon.

In computer languages, the second example with the variable L is called Lambda attachment, where the variable L isn't just a reference to the lambda, but changing the value of L requires calling the lambda with arguments, like so, in Ruby:

Code: [Select]
L = lambda { | arg1, arg2 | arg1+arg2 }
L.call(5,6) #returns 11


This is very similar to what we did in Axe, but the axe version is a lot more primitive, since it is only storing the address of the function into L, whereas the Ruby one does that and a few more thing, which gives it more control.  So, computer programmers, don't confuse the exact use of variable attachment between computer languages and Axe, you might find yourself doing things wrong in some cases.


VI - Closures and Currying

I know, "LOLOMG", those two terms sound stupid, right?  They'll sound a lot cooler once you realize how awesome the two concepts are ;-).  Both of these concepts build upon the idea of using lambdas.  Both of these concepts are really, really similar, but are different based only on the entity they're used within.

To start, well use closures.  Closures are functions that are embedded inside of other functions;  the embedded functions share the same argument variables as the outside function, as they get passed along in the embedded argument list.  There are two types of closures in Axe -- independent and dependent.  Independent ones are their own functions and can be called on their own; but when the main function is called, their arguments are the same as those of the larger function.  An example:

Code: [Select]
Lbl CAT
  r1+r2->r4
  DOG(r1,r2,r3,r4)
  Goto DOGE
  Lbl DOG
    r4+r3
    Return
  Lbl DOGE
  -> r1
  Return
Lbl CATE


Notice how when DOG is called, the arguments it is given are the exact same as what will be passed to CAT.  Like I said above, this is how closures will usually act when taking operands.  Dependent Closure's use are rare, but don't forget about them since they can still be lifesaving in terms of optimization when you need to only call part of a routine.  They are also messy to implement, which is why dependent closures usually take precedence.  Independent ones are closures that cannot be called from outside of the function; these are usually implemented via lambdas:

Code: [Select]
Lbl CAT
  r1+r2->r4
  λ(r4+r3)(r1,r2,r3,r4)->r1
  Return
Lbl CATE


as you can see, a lot cleaner than dependent ones :-) which is a big upside for organization and optimization.  The only downside is that they can't be called from outside the function, but that's usually okay in 99% of circumstances they're used.

Keep in mind using a lambda for so ethane that simple is a bit kooky, since without it it would be even more optimized.  I just use it as a light-hearted example :-).

Currying is very very similar to this.  A curry is a lambda within a lambda.  They share the same arguments in most cases, and the inner lambda helps the evaluation of the outer lambda.  Again, another silly example:

Code: [Select]
λ(r1*(λ(r1+r2)(r1,r2)))(r1,r2)


VII - Higher-level Functions

In this section, we'll be covering three specific types of functions, which are Maps, Folds, and Filters.  These three types of functions are called higher-level because they are usually made to take other functions (usually lambdas) as parameters, which they use to apply a rule on a set  of data, such as an array or list (we'll be working with an array of 8 bit values here)

The first one up -- mapping.  Mapping is the simplest of the three -- a map will take parameters for a function to apply to a set of data, which is a second parameter in most cases.  Here is the code body for a really simple map:

Code: [Select]
Lbl MAP
  For(A,r2,r2+r3)
    (r1)({A}) -> {A}
  End
Return


This one will take three parameters -- an address to a function (or a lambda since they will return an address), a starting address for our array, and the length of the array.  The first argument, the function, will take the value of the current index position in a loop over the array, apply the expression, and return it so it can be stored back into the array.  Here is an example of calling this simple map:

Code: [Select]
MAP(λ(r1*2),L1,50)

What this will do is multiply all of the first 50 bytes in L1 by 2, and store them back again.

Next up is folding.  Folding is somewhat like mapping, but it'll return something, such as a status or a pointer to a fixed array, after it runs through as a modified map.  Here is an example of a Fold:

Code: [Select]
Lbl FOLD
  r4 -> B
  For(A,r2,r2+r3)
    (r1)({A}) -> {B}
    B++
  End
  r4
Return


This is a really, really simple fold that maps the input function over the range specified, but it takes a fourth argument to specify where to put the fixed data.  It doesn't affect the elements of {r2} through {r2+r3}, but it does scan them and store the results starting at {r4}.  Since it is a fold, it needs to return something, so at the end it passes the original pointer to the new data into HL.  Here is our call:

Code: [Select]
FOLD(λ(r1+r2),L1,50,L2)

The last I'll discuss here is Filtering.  Filters take in a Function like the first two did, but the function in this case is testing to see if an element meets an expression; if it does, it will store the index place in an output array.  The output array is at most the size of the original array, and in many cases smaller if elements don't meet the expression.  It's also good to add one to each of the indices return, so we can null terminate the returned array.  Here is our simple filter function:

Code: [Select]
Lbl FILT
  r4 -> B
  For(A,r2,r2+r3)
    If (r1)({A})
      +1-> {B}:B++
    End
  End
  0 -> {B}
  r4
Return


We can call it like this:

Code: [Select]
FILT(λ(r1=5),L1,50,L2)

This call will return a pointer to L2, which will contain the indices of all elements plus 1 within the first 50 bytes of L1 that equal 5, null terminated.


VIII - Closing

I hope this taught everyone a little something about Axe functional programming; I tried to make it for all levels of experience, so everyone can get something out of it.  I had a fun time writing this (somewhat sarcastic when saying this, I did it all in the Notes app on my iPad and was a pain to type) and I hope you had a fun time reading this.  With that, salut, and I'm off to work soon so I wont be able to see any hate mail here for about 11 hours ;-)


IX - comments and all that crap

Questions?  Comments?  Feel free to post them below in this thread.  If you want some heads on help, feel free to shoot me a PM here.  And, if you want me to cover another concept, post what you want here and I can add something on it most likely ;-)

Rating: This article has not been rated yet.

Comments



Powered By SMF Articles by CreateAForum.com