The Salty Economist

Things I Should Have Learned in High School
posts - 56, comments - 0, trackbacks - 0

The Trouble with Tuples

Tuples are bizarre - what can I say.

So here's the scoop.  A Tuple is a variable that can have multiple values, but that does not have to be explitly defined.

For example, I have a data point (stock price) that has both a date and a closing price.  I can use the following to instantiate an instance and assign it values:

let aprice = Convert.ToDateTime("01/01/1990"), 25.50

Note, the values are separated by commas - commas are required.  The above statement says create an instance of the Tuple aprice that has a date of 01/01/1990 and a price of 25.50.

If I wanted to add an opening price, I would define it a such:

let aprice = Convert.ToDateTime("01/01/1990"), 25.50, 26.00

and so on.

You can now pass the Tuple into a function as a multi-valued parameter, which is good, but even better is that you can return a muli-valued return value.  Thus, I am not stuck with only getting one return value from a function or with the ugly alternative of passing arguments (by referernce) whose values can be changed by the function.

A major hiccup I have had with Tuples is that when I create a function or pass arguments to a function, I want to use commas.

For example, the function prototype 

let getFirstDate sd, ed, tick =

is actually expecting a Tuple, not three individual arguments.  We need to define it as:

let getFirstDate sd ed tick =

to make it actually have three arguments.

The same works with calling a function.  If I write:

let _,fprice = getFirstDate sd, ed, tick

I am actually passing a Tuple -- not three arguments.

I actually need to write:

let _,fprice = getFirstDate sd ed tick

To pass three individual arguments.  This will take some getting used to.

so let's say we have a Tuple defined as:

let fprice = Convert.ToDateTime("01/01/1990"), 25.50

How do we recover each individual element?  E.g. If I just want to use the price, how do I get its value?

It took me a long time to figure this one out.
But, at long last, I found two ways to do it.

(1)  You can use somthing called 'pattern matching'

If I just want the price from my Tuple fprice and I want to assign it to a variable xPrice, I would use the following statement:

let _, xPrice = fprice

Kinda weird, Huh?  This statement works like a mask and says "I don't care about the first element (hence the underscore),

just give me the second element and assign it to my scaler variable xPirce.

If I had the triple Tuple:

let fprice = Convert.ToDateTime("01/01/1990"), 25.50, 26.78

and I wanted to recover the middle element, I would use the statment:

let _, xPrice, _ = fprice

Of course, if I wanted to recover the last two values, I would use:

let _, xPrice, yPrice = fprice

to assign the last two values into the variables xPrice and yPrice, respectively.

One thing I still don't know how to do is how to use one element of a Tuple on the right side of an expression.  For example,

 what if I wanted to calculate the average of the last two elements.

I want to write a statement like:

let avgPrice = ( Tuple (element one) + Tuple (element two) ) / 2.0

I'll update this entry once I fugure this one out.

(2)  A second way to retrive a value is using the functions fst (first) and snd (second) for Tuples that only have two elements.

The statement:

let xPrice = (snd fprice)

retieves the second element from fprice.

Note:  These functions only work with Tuples that have two elements.  I have hunch that the whole concept of a Tuple was originally designed as just a two-valued variable or couplet Tuplet.

Overall, I think that Tuples have very limited use.

I much prefer to explicity create a data type like:

type clsprice = {adate : DateTime; price : double}

Note the curly braces.  You can create a variable that has mutiple values, each with a name and a type (int, double, DateTime).

In order to instantiate an instance of type clsprice, you use the following notation

let fprice = {adate = Convert.ToDateTime("01/01/1990"); price= 25.50}

The F# compiler is smart enough to look at the right side of this expression and know that the left hand variable is of type clsprice.

 The really cool thing about using a type structure is that you can then use the dot (.) notation to refer to an element. 

For example:

let xPrice = fprice.price

recover the element price from fprice.

This makes your code way more readable.  You get intellisense when you are coding.  Also, you can use this notation on the right side of an expression with no problem,  thereby overcoming the problem with Tuples that I noted above.

Thus, I can now write:

type clsprice = {adate : DateTime; openprice: double; closeprice: double}
let fprice = {adate = Convert.ToDateTime("01/01/1990"); openprice= 25.50; closeprice=26.50}
let avgPrice = ( fprice.openprice + fprice.closeprice ) / 2.0

Life is good.

So, now let's look at my getFirstDate function

let getFirstDate (sd : DateTime) (ed: DateTime) tick =

    let selsql = " SELECT TOP 1 ADATE, CLOSING_PRICE FROM TBL_PRICE_DATA WHERE CLOSING_PRICE > 0.001 AND TICKER_ID = " +

tick.ToString() + " AND ADATE BETWEEN '" + sd.ToString() + "' AND '" + ed.ToString() + "' ORDER BY ADATE "

    let conn = new System.Data.SqlClient.SqlConnection(connstr)
    let iOpen = conn.Open()

    let cmd = new System.Data.SqlClient.SqlCommand(selsql, conn)

    let reader = cmd.ExecuteReader()
    let iRead = reader.Read()

    let (xret : clsprice) =
        if iRead then
            {adate = reader.GetDateTime(0); price = reader.GetDouble(1)}
        else
            {adate = Convert.ToDateTime("01/01/1900"); price = 0.0}

    reader.Close()
    cmd.Dispose()
    conn.Dispose()
    xret.adate, xret.price

I pass in three parameters sd (start date) ed (end date) and tick (company Ticker ID).  Note that I explicitly type my date

variables.  The notation here is: variablename: type enclosed in parentheses.

In my sql string, I then need to use the .ToString() method in order to cast the arguments as strings.  Otherwise the

complier will yell at me telling me I can't add a date type to a string type.

The two interesting things to note with this function are:

(1)  The if .. then structure:

    let (xret : clsprice) =
        if iRead then
            {adate = reader.GetDateTime(0); price = reader.GetDouble(1)}
        else
            {adate = Convert.ToDateTime("01/01/1900"); price = 0.0}

 If F#, you have remember that everything is a function, even an if .. then construct.

So instead of writing something like:

    if i = 0 then
        x=1
    else
        x=2
 

we define x as the result of a function.

let x =
    if i = 0 then
        1
    else
        2

This is very much like the VB function IIF which looks like this:

x = IIF(i=0, 1, 2)

One important thing to note is that you need to have the else clause.

F# can't handle something like:

let x =
    if i = 0 then
        1

You will get the Error: "Error 1 This expression has type  int but is here used with type  unit"

I get this type mismatch because one leg of the if construct returns an integer and the other (implied) leg returns the anonymous unit type which generates a type mismatch -- integers and unit types are the same.

So what is a unit type?

According to Microsoft:

"The unit type has a single value, and that value is indicated by the token ().

The value of the unit type is often used in F# programming to hold the place where a value is required by the language syntax, but when no value is needed or desired. An example might be the return value of a printf function. Because the important actions of the printf operation occur in the function, the function does not have to return an actual value. Therefore, the return value is of type unit."

 The moral here is that all return paths in a function need to have the same type or else you will get a type mismatch.

Back to my if statement:

    let (xret : clsprice) =
        if iRead then
            {adate = reader.GetDateTime(0); price = reader.GetDouble(1)}
        else
            {adate = Convert.ToDateTime("01/01/1900"); price = 0.0}

This says return a value xret of type clsprice.  If iRead is true then return the date and the price from the reader; otherwise return dummy values 01/01/1900 and 0.0.  This is an example where I can return multiple values from an if function.

 (2)  The second item of note is how I pass back the return values to the caller.

    xret.adate, xret.price

The last statement in the function is just my two elements from my xret variable.  Note that the elements are split up and separated by a comma, thereby turning the return value into a Tuple.  Again, this is another example of passing back multiple values in a function.

 

Print | posted on Saturday, June 20, 2009 11:08 AM |

Powered by:
Powered By Subtext Powered By ASP.NET