I wrote the following to get clear on a couple of concepts in fan (Files, Maps):
// Example of files and maps
class Wordcount
{
Void main(Str[] args)
{
if (args.size != 1)
{
echo("Usage: Wordcount file_to_read")
Sys.exit(-1)
}
// Set up our map to count each word, and set its default to zero
word_counts := [Str:Int][:]
word_counts.def = 0
try
{
// Open the file, read each line in order
in_file := Uri(args[0]).toFile
in_file.eachLine |Str str|
{
// split on whitespace into words
words := str.split
// count each one
words.each |word|
{
word_counts[word] += 1
}
}
// Show each word found, with its count, in alphabetical order
keys := word_counts.keys
keys.sort.each |key|
{
echo("$key ${word_counts[key]}")
}
}
catch (IOErr ioe)
{
echo("Caught $ioe")
}
}
}
I'd like to donate it as an example for the cookbook. Please let me know if anything is "non-standard" fan -- the only thing I'm doing which I don't usually do is make single-use variables (such as words and keys above), which I'm doing for explanation in the code above.
My puzzle is that, if I run it on some random text file I have lying around, it works fine. However, if I run it on its own source code, I wind up with five zero-length (!!!) strings being found. I found this out by changing the final echo to
echo("$key ${key.size()} ${word_counts[key]}")
I'm running on a mac with java version "1.5.0_19".
I'd be curious to know if it's happening for anyone else... I've run od -c on the file, and there are no special characters in the file.
tompalmerSat 29 Aug 2009
It comes from your five empty lines. This behavior is also specified in the Str#split docs. (Though I looked it up only after investigating the program first.)
brianSat 29 Aug 2009
very cool
I added into a new examples\sys directory - changeset
As Tom mentioned, the specification of Str.split with an empty string is to return a list of one element. Fix is to just skip empty lines.
Regarding conventions, I tweaked a couple of things:
typically I wouldn't catch an error in a simple example program since it adds noise from what we are trying to show
Fan uses camel case convention rather than underbars
I prefer to put things like setting the def value of a map in an it-block as part of the construction process
But other than that, looks like pretty nice Fan code.
Thanks much!
yachrisSat 29 Aug 2009
Hi Brian,
Thanks for the explanation, and the kind words. I put in the try/catch since when it's run on a non-existent file, you get 24 lines (!!!) of backtrace. Not exactly user-friendly :-/ as opposed to the one-line output of the catch output.
However, one thing I kind of don't like:
// skip empty lines
if (line.trim.isEmpty) return
In the context of an eachLine I guess it makes sense to use a return, but my feeling is that it really should be a continue, since it's conceptually a loop; the return looks like it's returning from the main.
brianSat 29 Aug 2009
In the context of an eachLine I guess it makes sense to use a return, but my feeling is that it really should be a continue, since it's conceptually a loop;
That is actually a "tricky" aspect of Fan - closures are true nested functions. This means that return actually returns from the closure (not the containing method).
So whenever you see a closure open |...| it means that you are in a nested function.
To avoid real confusion, we don't allow the return keyword to be used inside an it-block (which is itself just a closure).
yachrisSat 29 Aug 2009
Okay, I understand what's happening from the compiler's point of view.
It just is kind of jarring... violates the Principle of Least Surprise(1). Of course, I'm coming from rubylandia, where next (Ruby's equivalent to continue) does The Right Thing(TM) inside eachLine type loops.
And, I think it's worth considering that inside (say) an eachLine, it would be nice to return from the method we're currently in, and return is a nice way to do that.
~~~~~~~~~~~~~~~
(1) The Principle of Least Surprise is just an encoding of my prejudices, set in formal language to compel your instant obedience :-)
brianSat 29 Aug 2009
I can definitely understand that, but it depends on your perspective. If you think of a closure a loop, then it is inconsistent. But if you think of a closure as a nested function then it is consistent. Fan actually treats functions in a much more consistent way than Ruby blocks.
We have had several discussions about a non-local return from within a closure. The general consensus (and many disagree) is that it is too complicated because under the covers it requires throwing an exception. Typically where you would do a continue you can use a return, and where you would do a break you use a specialized iterator such as sys::List.find or sys::List.eachWhile.
yachris Sat 29 Aug 2009
Hello,
I wrote the following to get clear on a couple of concepts in fan (Files, Maps):
I'd like to donate it as an example for the cookbook. Please let me know if anything is "non-standard" fan -- the only thing I'm doing which I don't usually do is make single-use variables (such as
words
andkeys
above), which I'm doing for explanation in the code above.My puzzle is that, if I run it on some random text file I have lying around, it works fine. However, if I run it on its own source code, I wind up with five zero-length (!!!) strings being found. I found this out by changing the final
echo
toI'm running on a mac with java version "1.5.0_19".
I'd be curious to know if it's happening for anyone else... I've run
od -c
on the file, and there are no special characters in the file.tompalmer Sat 29 Aug 2009
It comes from your five empty lines. This behavior is also specified in the Str#split docs. (Though I looked it up only after investigating the program first.)
brian Sat 29 Aug 2009
very cool
I added into a new examples\sys directory - changeset
As Tom mentioned, the specification of Str.split with an empty string is to return a list of one element. Fix is to just skip empty lines.
Regarding conventions, I tweaked a couple of things:
But other than that, looks like pretty nice Fan code.
Thanks much!
yachris Sat 29 Aug 2009
Hi Brian,
Thanks for the explanation, and the kind words. I put in the
try/catch
since when it's run on a non-existent file, you get 24 lines (!!!) of backtrace. Not exactly user-friendly :-/ as opposed to the one-line output of thecatch
output.However, one thing I kind of don't like:
In the context of an
eachLine
I guess it makes sense to use areturn
, but my feeling is that it really should be acontinue
, since it's conceptually a loop; thereturn
looks like it's returning from themain
.brian Sat 29 Aug 2009
That is actually a "tricky" aspect of Fan - closures are true nested functions. This means that
return
actually returns from the closure (not the containing method).So whenever you see a closure open
|...|
it means that you are in a nested function.To avoid real confusion, we don't allow the
return
keyword to be used inside an it-block (which is itself just a closure).yachris Sat 29 Aug 2009
Okay, I understand what's happening from the compiler's point of view.
It just is kind of jarring... violates the Principle of Least Surprise(1). Of course, I'm coming from rubylandia, where
next
(Ruby's equivalent tocontinue
) does The Right Thing(TM) insideeachLine
type loops.And, I think it's worth considering that inside (say) an
eachLine
, it would be nice to return from the method we're currently in, andreturn
is a nice way to do that.~~~~~~~~~~~~~~~
(1) The Principle of Least Surprise is just an encoding of my prejudices, set in formal language to compel your instant obedience :-)
brian Sat 29 Aug 2009
I can definitely understand that, but it depends on your perspective. If you think of a closure a loop, then it is inconsistent. But if you think of a closure as a nested function then it is consistent. Fan actually treats functions in a much more consistent way than Ruby blocks.
We have had several discussions about a non-local return from within a closure. The general consensus (and many disagree) is that it is too complicated because under the covers it requires throwing an exception. Typically where you would do a continue you can use a return, and where you would do a break you use a specialized iterator such as
sys::List.find
orsys::List.eachWhile
.