Something we might want to keep track of is a list of Fan "Gotchas" for people coming from other languages. If you come up with any more, list them here:
Int[] list := Int[1024] creates a list with a single element: the integer 1024. To do Java's equivalent of new int[1024] in Fan, use the List.fill method.
Use := for initializing a variable, not =.
The empty list literal is [,] not [] and the empty map is [:] not [] or {}.
(Correct me if I'm wrong on this) When using the comma notation in an it-block, such as MyThing { obj1, obj2, obj3 }, the compiler constructs this as MyThing.make.add(obj1).add(obj2).add(obj3). You need to make sure that MyThing.add has a This return type!
The default construct is called make and does not share the name of the class. It is marked with the new keyword in place of a return type.
The compiler automatically inserts downcasts when it can't see the cast is impossible. For example, the code Obj x := 42; Str y = x; compiles but throws a runtime error.
Characters are represented in Fan as Ints corresponding to the character's Unicode code points. Be careful when appending characters to the end of a string! The code "Lol" + s'' results in the string "Lol115", not "Lols" like you might expect. Use the toChar method to convert Ints to Strs.
...since List.remove(Obj) is removing an Int that may or may not still be in the list; removeAt(index) is what I wanted.
Most of the time you'll get a compile error there, but not when the index and the content are of the same type.
tompalmerWed 6 May 2009
When using the comma notation in an it-block, such as MyThing { obj1, obj2, obj3 }, the compiler constructs this as MyThing.make.add(obj1).add(obj2).add(obj3). You need to make sure that MyThing.add has a This return type!
Just tried it, and that's what I get, too. I personally think this should be changed. It should do repeated calls to add on the original object, in my opinion.
tompalmerWed 6 May 2009
Or perhaps some kind of addAll, if it exists?
qualidafialWed 6 May 2009
It should do repeated calls to add on the original object, in my opinion.
Wouldn't this break on a const class? i.e. MyImmutable.add may return a new MyImmutable that is identical save for the additional collection element.
brianWed 6 May 2009
Just tried it, and that's what I get, too. I personally think this should be changed. It should do repeated calls to add on the original object, in my opinion.
Technically a list of expressions separated by a comma is a single expression itself which is why it makes sense to chain the adds together. That and it is more efficient. I debated this, and decided this is the right way (despite being a gotcha). BTW this is how normal list literals work too:
[0, 1, 2] => List.make(Int#).add(0).add(1).add(2)
Or perhaps some kind of addAll, if it exists?
That would be a more expensive since you'd have to build a list first (just to destructure it again).
Wouldn't this break on a const class?
That is another reason I decided to make it a single chained expression. Although write now the result can't be assigned to anything, so we still can't really use it for immutables.
tacticsWed 6 May 2009
Technically a list of expressions separated by a comma is a single expression itself which is why it makes sense to chain the adds together.
Additionally, it supports the case where a class has an "add()" function which returns a modified copy instead of using side effects. (Or at least, I think that's possible).
Gotchas are fine in a language as long as they don't do the opposite of what you expect.
Though in this case, if add() IS used in a comma expression, it might make sense to throw a more informative error. Right now, the error generated is triggered by the code after de-sugaring, but it doesn't seem too hard to do a check before de-sugaring.
qualidafialWed 6 May 2009
Does the compiler generate an error if an add expression in an it-block is applied to a type where the add method returns something other than This?
brianWed 6 May 2009
Though in this case, if add() IS used in a comma expression, it might make sense to throw a more informative error.
That is a good idea, I will add a meaningful error message.
tacticsWed 6 May 2009
Does the compiler generate an error if an add expression in an it-block is applied to a type where the add method returns something other than This?
It throws whatever errors the expansion would be. For example, if MyThing.add is defined as a Void-valued function, then with the code:
MyThing
{
obj1,
obj2,
obj3
}
The resulting expansion inside the compiler would be
MyThing.make.add(obj1).add(obj2).add(obj3)
And the result is an error message like "Cannot call .add on a Void-valued expression.
alexlamslThu 7 May 2009
Although {right} now the result can't be assigned to anything, so we still can't really use it for immutables.
Would you mind elaborating on that point? I would expect MyThing{obj1,obj2,obj3} to work even with:
const class MyThing {
const Obj[] elements
This add(Obj e) {
Obj[] list = new Obj[,]
list.addAll(elements)
list.add(e)
return MyThing{elements = list.toImmutable}
}
}
So with is the one returning the value. I suppose it could return the return value of the it-block, which would make the example work, but then what to do for most it-blocks that have no need to return any value???
alexlamslThu 7 May 2009
Ah I see - the sugar hypes me out, eventually >____<"
In this case, I guess with could make a judgement between |This->Void| and |This->This|, with any other cases being illegal it-blocks.
brianFri 8 May 2009
Though in this case, if add() IS used in a comma expression, it might make sense to throw a more informative error.
I added a better error message.
qualidafialFri 8 May 2009
Should it-blocks return This if they use the comma syntax to add items? This aligns with the requirement that the add method returns This, and would enable alexlamsl's const usage above, thus:
Of course sys::Obj.with would then have to distinguish between it-blocks that return Void (and in those cases return this) and it-blocks that return This (and in those cases return the result from the block). Not sure if this would be possible.
brianFri 8 May 2009
Should it-blocks return This if they use the comma syntax to add items? This aligns with the requirement that the add method returns This, and would enable alexlamsl's const usage above, thus:
I don't think we can really do that without forcing everyone to return a value from a with-it-block (which would be annoying).
But you can solve the problem with immutable classes using a builder:
const class Foo
{
new make(|Obj[]| f)
{
builder := Obj[,]
f(builder)
list = builder
}
static Void main()
{
x := Foo { 0, 1, 2, 3 }
echo(x.list)
}
const Obj[] list
}
alexlamslFri 8 May 2009
I don't think we can really do that without forcing everyone to return a value from a with-it-block (which would be annoying).
It would be annoying only if you have to type out return it explicitly.
Perhaps we can have return it as an implicit statement unless a return statement exists within the it-block?
JohnDGFri 8 May 2009
Perhaps we can have return it as an implicit statement unless a return statement exists within the it-block?
Self-return is a special and tedious case. It would be interesting to toy with the idea of auto returning it/this depending on the return type. Basically what Void does now.
yachrisSat 8 Aug 2009
Here's a new gotcha (or maybe I'm an idiot :-)
I was looking at:
#! /usr/bin/env fan
using fwt
class FwtHello : Test
{
Void main()
{
Window { Label { text = "Hello world" }, }.open
}
}
and refactoring towards more functionality (I wanted to bind a Button to a method, but that's irrelevant to this discussion) so I wanted to move the Window creation out to a method. So I did this:
#! /usr/bin/env fan
using fwt
class FwtHello : Test
{
Void main()
{
fh := FwtHello()
}
new make()
{
Window { Label { text = "Hello world" }, }.open
}
}
Which seems reasonable, until you run it. It runs fine, but if you click the window's close box, the window comes back immediately! The second close of the window makes it go away for real. And if I use Command-Q (the mac's key-binding to quit a program) I get a lovely backtrace. For the record, I first saw the double-window-show at work on Windows, so it's not a Mac thing.
Finally, I did the following:
#! /usr/bin/env fan
using fwt
class FwtHello : Test
{
Void main()
{
fh := FwtHello()
fh.open()
}
new make()
{
m_window = Window { Label { text = "Hello world" }, }
}
Void open()
{
m_window.open()
}
Window m_window
}
which (A) works and (B) makes sense, but... I'm confused. Why would the second one do the double-window thing? And should this be explained somewhere?
Thanks!
brianSat 8 Aug 2009
I haven't tried to run your code, but I expect it is because your main is instance based and not static, so when you run main what you are really doing is this:
FwtHello().main // create instance of FwtHello
Try changing your main to static
yachrisSat 8 Aug 2009
Try changing your main to static
Bingo! That fixed it. Thanks.
Raises the interesting question... should a non-static main function the same as a static main?
tacticsSat 8 Aug 2009
A non-static main instantiates an object of the class its in and then runs it. It's convenient in some cases where you have a manager or launcher class. But I think it does classify as a gotcha, so thanks for bringing it to the list.
andySun 9 Aug 2009
should a non-static main function the same as a static main?
Depends how you structure your code. As tatics points out, because you invoked open in your ctor and used an instance main, the ctor will get called twice, once to create a new instance, and then once more inside your main method.
Sticking the whole code block inside your main method, the behavoir will be the same for an instance and a static main method.
tompalmerMon 10 Aug 2009
I love the availability and convenience of non-static main, by the way. It does exactly what I want it to do.
tactics Tue 5 May 2009
Something we might want to keep track of is a list of Fan "Gotchas" for people coming from other languages. If you come up with any more, list them here:
Int[] list := Int[1024]creates a list with a single element: the integer 1024. To do Java's equivalent ofnew int[1024]in Fan, use theList.fillmethod.:=for initializing a variable, not=.[,]not[]and the empty map is[:]not[]or{}.MyThing.make.add(obj1).add(obj2).add(obj3). You need to make sure thatMyThing.addhas aThisreturn type!makeand does not share the name of the class. It is marked with thenewkeyword in place of a return type.Obj x := 42; Str y = x;compiles but throws a runtime error.Ints corresponding to the character's Unicode code points. Be careful when appending characters to the end of a string! The code"Lol" +s'' results in the string "Lol115", not "Lols" like you might expect. Use thetoCharmethod to convertInts toStrs.KevinKelley Tue 5 May 2009
Here's one that got me today...
digits := [1,2,3,4,5,6,7,8,9] chosen := [,] 4.times { chosen.add(digits.remove(Int.random(0..<digits.size))) }to randomly pick 4 digits without repeating. Of course that gives a NullErr every now and then...
4.times { chosen.add(digits.removeAt(Int.random(0..<digits.size))) } ^^...since List.remove(Obj) is removing an Int that may or may not still be in the list; removeAt(index) is what I wanted.
Most of the time you'll get a compile error there, but not when the index and the content are of the same type.
tompalmer Wed 6 May 2009
Just tried it, and that's what I get, too. I personally think this should be changed. It should do repeated calls to add on the original object, in my opinion.
tompalmer Wed 6 May 2009
Or perhaps some kind of addAll, if it exists?
qualidafial Wed 6 May 2009
Wouldn't this break on a
constclass? i.e. MyImmutable.add may return a new MyImmutable that is identical save for the additional collection element.brian Wed 6 May 2009
Technically a list of expressions separated by a comma is a single expression itself which is why it makes sense to chain the adds together. That and it is more efficient. I debated this, and decided this is the right way (despite being a gotcha). BTW this is how normal list literals work too:
That would be a more expensive since you'd have to build a list first (just to destructure it again).
That is another reason I decided to make it a single chained expression. Although write now the result can't be assigned to anything, so we still can't really use it for immutables.
tactics Wed 6 May 2009
Additionally, it supports the case where a class has an "add()" function which returns a modified copy instead of using side effects. (Or at least, I think that's possible).
Gotchas are fine in a language as long as they don't do the opposite of what you expect.
Though in this case, if add() IS used in a comma expression, it might make sense to throw a more informative error. Right now, the error generated is triggered by the code after de-sugaring, but it doesn't seem too hard to do a check before de-sugaring.
qualidafial Wed 6 May 2009
Does the compiler generate an error if an add expression in an it-block is applied to a type where the
addmethod returns something other thanThis?brian Wed 6 May 2009
That is a good idea, I will add a meaningful error message.
tactics Wed 6 May 2009
It throws whatever errors the expansion would be. For example, if
MyThing.addis defined as aVoid-valued function, then with the code:MyThing { obj1, obj2, obj3 }The resulting expansion inside the compiler would be
And the result is an error message like "Cannot call
.addon aVoid-valued expression.alexlamsl Thu 7 May 2009
Would you mind elaborating on that point? I would expect
MyThing{obj1,obj2,obj3}to work even with:const class MyThing { const Obj[] elements This add(Obj e) { Obj[] list = new Obj[,] list.addAll(elements) list.add(e) return MyThing{elements = list.toImmutable} } }JohnDG Thu 7 May 2009
The problem is in the expansion:
MyThing{obj1,obj2,obj3,}Desugars to:
MyThing.make().with({ add(obj1).add(obj2).add(obj3) })So
withis the one returning the value. I suppose it could return the return value of the it-block, which would make the example work, but then what to do for most it-blocks that have no need to return any value???alexlamsl Thu 7 May 2009
Ah I see - the sugar hypes me out, eventually >____<"
In this case, I guess with could make a judgement between
|This->Void|and|This->This|, with any other cases being illegal it-blocks.brian Fri 8 May 2009
I added a better error message.
qualidafial Fri 8 May 2009
Should it-blocks return
Thisif they use the comma syntax to add items? This aligns with the requirement that theaddmethod returnsThis, and would enable alexlamsl's const usage above, thus:MyThing { obj1, obj2, obj3 }would desugar to:
MyThing.make().with({ return add(obj1).add(obj2).add(obj3) })Of course
sys::Obj.withwould then have to distinguish between it-blocks that returnVoid(and in those cases returnthis) and it-blocks that returnThis(and in those cases return the result from the block). Not sure if this would be possible.brian Fri 8 May 2009
I don't think we can really do that without forcing everyone to return a value from a with-it-block (which would be annoying).
But you can solve the problem with immutable classes using a builder:
const class Foo { new make(|Obj[]| f) { builder := Obj[,] f(builder) list = builder } static Void main() { x := Foo { 0, 1, 2, 3 } echo(x.list) } const Obj[] list }alexlamsl Fri 8 May 2009
It would be annoying only if you have to type out
return itexplicitly.Perhaps we can have
return itas an implicit statement unless areturnstatement exists within the it-block?JohnDG Fri 8 May 2009
Self-return is a special and tedious case. It would be interesting to toy with the idea of auto returning it/this depending on the return type. Basically what
Voiddoes now.yachris Sat 8 Aug 2009
Here's a new gotcha (or maybe I'm an idiot :-)
I was looking at:
#! /usr/bin/env fan using fwt class FwtHello : Test { Void main() { Window { Label { text = "Hello world" }, }.open } }and refactoring towards more functionality (I wanted to bind a Button to a method, but that's irrelevant to this discussion) so I wanted to move the Window creation out to a method. So I did this:
#! /usr/bin/env fan using fwt class FwtHello : Test { Void main() { fh := FwtHello() } new make() { Window { Label { text = "Hello world" }, }.open } }Which seems reasonable, until you run it. It runs fine, but if you click the window's close box, the window comes back immediately! The second close of the window makes it go away for real. And if I use Command-Q (the mac's key-binding to quit a program) I get a lovely backtrace. For the record, I first saw the double-window-show at work on Windows, so it's not a Mac thing.
Finally, I did the following:
#! /usr/bin/env fan using fwt class FwtHello : Test { Void main() { fh := FwtHello() fh.open() } new make() { m_window = Window { Label { text = "Hello world" }, } } Void open() { m_window.open() } Window m_window }which (A) works and (B) makes sense, but... I'm confused. Why would the second one do the double-window thing? And should this be explained somewhere?
Thanks!
brian Sat 8 Aug 2009
I haven't tried to run your code, but I expect it is because your main is instance based and not static, so when you run main what you are really doing is this:
Try changing your main to static
yachris Sat 8 Aug 2009
Bingo! That fixed it. Thanks.
Raises the interesting question... should a non-static main function the same as a static main?
tactics Sat 8 Aug 2009
A non-static main instantiates an object of the class its in and then runs it. It's convenient in some cases where you have a manager or launcher class. But I think it does classify as a gotcha, so thanks for bringing it to the list.
andy Sun 9 Aug 2009
Depends how you structure your code. As tatics points out, because you invoked
openin your ctor and used an instance main, the ctor will get called twice, once to create a new instance, and then once more inside yourmainmethod.Sticking the whole code block inside your
mainmethod, the behavoir will be the same for an instance and a staticmainmethod.tompalmer Mon 10 Aug 2009
I love the availability and convenience of non-static main, by the way. It does exactly what I want it to do.