Sunday, June 29, 2008

Geo-conversion finished, for now.

Well, I got my little geo-conversion program finished. Nothing too fancy, but it works. I think I'd like to see if I can use a arbitrary-precision library to do the calculations instead. Right now, everything is double, but I'm not sure how much it matters. Hell, I don't even know how many digits past the decimal point anybody really cares about.

Anyway, here's the code:


import Text.ParserCombinators.Parsec
import Data.List

-- This data type stores the three pieces of info we need from each line.
data ConvLine = ConvLine {conversion::String, value::String} deriving Show

-- This function parses each line of input.
parseLines = do
lines <- many inputLines
eof
return lines

-- Separate each line of the input into two parts: the conversion to be
-- performed and the value to convert.
inputLines = do
conversion <- many1 (letter <|> char ':')
spaces
value <- anyChar `manyTill` newline
return (ConvLine conversion value)

-- This does the conversion. Using the whole conversion key as the different
-- cases.
geoConvert :: [ConvLine] -> [String]
geoConvert l = map doConvert l
where doConvert s = case (conversion s) of
"DMS:DD" -> dmsToDD (value s)
"DD:DMS" -> ddToDMS (value s)
_ -> error $ "Undefined conversion: " ++ show(s)

-- Convert DMS to Decimal Degrees.
dmsToDD :: String -> String
dmsToDD s = case (parse parseDMSToDD "" s) of
Left err -> error $ "Input:\n" ++ show s ++
"\nError:\n" ++ show err
Right result -> show result

-- Convert Decimal Degrees to DMS.
ddToDMS :: String -> String
ddToDMS s = case (parse parseDDToDMS "" s) of
Left err -> error $ "Input:\n" ++ show s ++
"\nError:\n" ++ show err
Right result -> result

-- This one is simple. First, break apart the different pieces of the value
-- into their numeric components. Next, calculate and return the Decimal
-- Degrees value.
-- Key point here: read takes a string and converts it to a number if
-- possible.
parseDMSToDD :: Parser Double
parseDMSToDD = do
degrees <- many1 digit
char 'D'
minutes <- many1 digit
char 'M'
seconds <- many1 digit
char 'S'
return ((read degrees) + ((read minutes) / 60) + ((read seconds) / 3600))

-- This parses out the number from the value and passes it to the calcDMS
-- function.
parseDDToDMS :: Parser String
parseDDToDMS = do
decimalDeg <- many1 (digit <|> char '.')
return (calcDMS (read decimalDeg))

-- This function creates the DMS string.
calcDMS :: Double -> String
calcDMS dd = (show (getDegrees dd)) ++ "D" ++
(show (getMinutes dd)) ++ "M" ++
(show (getSeconds dd)) ++ "S"

-- This function gets the degrees part.
getDegrees :: Double -> Integer
getDegrees n = (truncate n)

-- This function gets the minutes part.
-- Key point here: need to use fromInteger to convert from an Integer to a
-- Double.
getMinutes :: Double -> Integer
getMinutes n = (truncate ((n - (fromInteger $ getDegrees n)) * 60))

-- This gets the full value for the minutes.
getFullMinutes :: Double -> Double
getFullMinutes n = ((n - (fromInteger $ getDegrees n)) * 60)

-- This function gets the seconds part.
getSeconds :: Double -> Double
getSeconds n = (((getFullMinutes n) - (fromInteger $ getMinutes n)) * 60)

main = do
-- Read from stdin.
input <- getContents

-- Get the result of the parsing.
-- Left == error
-- Right == success
let convLines = case (parse parseLines "stdin" input) of
Left err -> error $ "Input:\n" ++ show input ++
"\nError:\n" ++ show err
Right result -> result

-- Do the conversion.
let outLines = geoConvert convLines
print outLines


My only complaint is how I had to do the conversion from Decimal Degrees to DMS. The various parts of the process are reusable, however I really wish I could have gotten it to work in only one function. When I tried it, the "return ..." statement at the end kept throwing a compilation error. Oh well, I'm probably just missing something. I'm sure I'll figure it out later.

UPDATE:

O.K., I really shouldn't post when I'm not thinking clearly as I still have at least one thing to do: convert to/from radians. I was also planning to convert between lats/longs and UTM/MGRS grid coordinate systems, however those are much more complicated to do, so I will most likely be putting them off for a while. I may have to deal with them at work, but until then I don't want to spend a lot of time on them since the real purpose of this exercise is to learn Haskell and not how to do the conversions.

Another note, as to how this program works, it reads from STDIN and outputs to STDOUT. I'm expecting the data to be stored in a text file or it can be streamed it. The format is very simple:

operation value

Where operation is "DMS:DD" or "DD:DMS" and the value is the value you want to convert. Currently, only the 1D2M3S format for Degress Minutes Seconds is supported for simplicity.

As I'm still under the weather right now but unable to sleep, I bid you all farewell and good night.

Labels:

Tuesday, June 24, 2008

Converting Geosptial Coordinates

Well, I've been focusing on Haskell of late mainly because there's more for me to learn. As I believe I stated before, D is similar to what I've learned before, so it's easier for me to pick up. On the other hand, I've been spending more time getting used to the way Haskell works.

One thing that I'm itching to do is create a useful little program right now. I just read several sections in Haskell book/tutorial on wikibooks and at least one other tutorial, so I think I know almost as much as I need to know. The problem was, what type of program to create.

At work, we're dealing with some geospatial data and the one thing I learned yesterday was how coordinates are represented in some grid-based coordinate systems. During the research we were doing, the guy I'm working with found a link with a number of formulas for converting between the different coordinate systems. Score! I can make a small app that reads from stdin, converts a list of values from one coordinate system to another, and outputs them to stdout. This way, data can be piped through easily.

I know this isn't the most useful app, but it seems like just the kind of thing I would want to do in a language like Haskell since we want it to work right every single time without fail. Now I just have to define the input/output formats and see how much I like Text.printf :-)

Labels: ,

Monday, June 16, 2008

Area/Volume Programs

Well, over the weekend and today, I created two programs that calculate areas. I'll put them below, but for now, I just want to point out some things I observed.

First, the Haskell tutorials are still giving me trouble. I'm doing a little better, but I had a bit of trouble trying to concatenate a number to a string. I thought it could be done with the ++ operator, but no, it would fail. It turns out you need to call a function called show to turn a number into a string or something like that. I never saw it in any of the basic tutorials.

Second, the compile time of GHC is very noticeable now. Here are some numbers:

GHC:
real 0m1.370s
user 0m0.472s
sys 0m0.104s

GDC:
real 0m0.243s
user 0m0.104s
sys 0m0.020s

Big difference for such a small program. Not really sure why this is happening, nor do I think I care too much right now. Of course, as programs get bigger, so do compile times. I wonder if this discourages some users from using a language like Haskell.

On the bright side, I'm liking Haskell's math capabilities more and more. You see, I wrote the Haskell version of this program using a tutorial. Well, I went beyond the tutorial, but that's besides the point. Tonight, I wrote the D version and I compared the outputs. It's very interesting to see the difference in precision that you get from the languages. Haskell is more precise and the runtime doesn't seem any different, thought it appears there is a difference:

GHC (ghc -o a area.hs):
real 0m0.004s
user 0m0.000s
sys 0m0.000s

GDC (gdc area.d):
real 0m0.015s
user 0m0.000s
sys 0m0.004s

The numbers are really to small to really tell a difference and I didn't do any optimizations, so who knows if this is accurate. I put the commands I'm using to do the compilation in parenthesis in case someone is curious.

All in all, I think I'm going to stick with the current pattern of doing something in Haskell first, then in D. I can understand and write code in D much faster than Haskell, so I figure I'd learn the "harder" language first, then write code in the "easier."

Anyway, here are the Haskell and D versions of my program:

Haskell:
{- Calculate the area of a circle. -}
circarea r = pi * r ^ 2;

{- Calculate the area of a triangle. -}
triarea b h = (b * h) / 2;

{- Calculate the area of a rectangle. -}
rectarea l w = l * w;

{- Calculate the area of a square. -}
sqarea s = rectarea s s;

{- Calculate the volume of a box. -}
boxvolume l w h = h * (rectarea l w);

{- Calculate the volume of a cylinder. -}
cylvolume r h = (circarea r) * h;

main = do
{
{- Apparently, the "show" function is required to convert a number
- to a string? -}
putStrLn ("Circle Area: " ++ (show (circarea 2)));
{- print and putStrLn behave differently. Observe the output. -}
print ("Triangle Area: " ++ (show (triarea 2 2)));
putStrLn ("Rectangle Area: " ++ (show (rectarea 2 2)));
putStrLn ("Square Area: " ++ (show (sqarea 2)));
putStrLn ("Box volume: " ++ (show (boxvolume 2 2 2)));
putStrLn ("Cylinder volume: " ++ (show (cylvolume 2 2)));
}
D:
import std.stdio; // Module for console IO.
import std.math;

real circarea(real r)
{
return PI * pow(r,2);
}

real triarea(real b, real h)
{
return (b * h) / 2;
}

real rectarea(real l, real w)
{
return l * w;
}

real sqarea(real s)
{
return rectarea(s, s);
}

real boxvolume(real l, real w, real h)
{
return rectarea(l, w) * h;
}

real cylvolume(real r, real h)
{
return circarea(r) * h;
}

int main(char[][] args)
{
writefln("Circle Area: %f", circarea(2));
writefln("Triangle Area: %f", triarea(2, 2));
writefln("Rectangle Area: %f", rectarea(2, 2));
writefln("Square Area: %f", sqarea(2));
writefln("Box Area: %f", boxvolume(2, 2, 2));
writefln("Cylinder Area: %f", cylvolume(2, 2));
return 0;
}

Saturday, June 14, 2008

First set of lessons/code

Well, I just wrote a few very simple programs and it's been interesting. I did a "Hello World" in both. D was very easy, however it took a while for me to figure out Haskell as most tutorials are done using an interpreter, which apparently is very different from using a compiler, which is what I was trying to do. I'm personally not liking this aspect of the language because I don't know of any other language that does that. Perhaps it's just the quality of the tutorials, but I think that code written in an interpreter should be identical to what you have to compile using a compiler.

Anyway, I have a factorial program written for each. Well, I have two for Haskell as there are two very different ways according to the one tutorial I was looking at. I'll just show you the cleaner one.

First, D:
import std.stdio; // Module for console IO.

int main(char[][] args)
{
int value = 4;
int factorial = value;
while (--value)
{
factorial *= value;
}
writefln("Result: %d",factorial);
return 0;
}
And now Haskell:

{- Factorial when input == 0 -}
fac 0 = 1

{- Factorial in all other cases -}
fac n = n * fac(n-1)

main = print (fac 42)
Both are very easy to understand and clean. The biggest difference between the two isn't the syntax, but the result. In D, the factorial of 42 was too big for the integer variable to handle, so it kept returning 0. However, Haskell easily printed out what I'm guessing is the correct result. This threw me off because I was expecting to see a similar result from the D version instead of 0.

While I haven't fallen in love with Haskell yet, mainly because it's very different from what I'm used to, I do like the fact that it does that be default instead of requiring an external library that may or may not integrate cleanly with the language. I'll have to read up on it some more, but I think that it'll be nice for applications that need arbitrary-precision math capabilities.

Going back to the syntax, the Haskell one is much easier to read in this case, but this is a basic math problem, so I would expect this. I do know that it'll take me some time to be comfortable with it, but I'm definitely willing to push forward and learn as much as I can stand, especially if I can use it to make reliable apps that don't need C-like speed, but do need something better than Perl.

Labels: , ,

Wednesday, June 11, 2008

Learning new langauges

Well, I've finally decided to learn a new language. Two actually. D and Haskell. Here's the way I figure it. I already know a good scripting language: Perl. While Lua interests me because of it's embeddable nature, I couldn't see anything I would use it for personally. Perl has many more useful libraries. Also, since I know Perl, I don't see the need to learn Python, Ruby, or any other scripting language because I don't see any reason to do so. They don't provide anything that I can't already to in Perl and if I did have to work with any of them, I'm pretty sure I can pick up a reference, look at a few tutorials, and work through any code I need to.

Now, why D? Easy: I wanted to learn a C-like language and D looked the best to me because it doesn't make me have to deal with character strings. What I mean is, I don't have to allocate/deallocate/check buffer lengths, etc. Having a native string type is very nice to me. Also, it has a variety of nice features that look pretty good and it looks to be well designed. Not perfect, but good. Also, since it can work with C libraries, I can resuse a lot of existing code. The only thing I lose compared to C is a little performance, but I think it's worth it at least for the added safety of the language.

Now, why Haskell? Simple: I feel the need to learn a functional programming language. After looking at it a bit more, it seems to have a nich between D and Perl in terms of performance and programmability. If I need speed, I can use D. If I need to come up with a quick script, I can use Perl. If I need to write a good app, I can turn to Haskell for something that's faster than Perl, but potentially, I'm not sure yet, safer than D and hopefully easier to code in. Also, I think that learning how to program in a functional language would be good for me.

Anyway, now the big problem is time :-)

Labels: , , , , , ,