The more I think about it, the more it seems to me that Fan's current signature notation is not consistent nor does it work well with Fan's currying.
About consistency:
1) Parameters are sometimes "inside" and sometimes "outside". I understand Brian's explanations and I think I should be able to write Fan code without making the mistakes I made recently, but I still think this could be simplified.
2) Declarations are "return type, parameter 1, parameter 2" while the closure syntax is "parameter 1, parameter 2 -> return type".
Since Fan supports currying, we should look at the return type as just another parameter, and after thinking about this more, I think that Haskell's notation might solve all these problems (note: I'm very much a beginner in Haskell, but at least that's one part that I think I understand).
For example, let's look at the following Java method:
boolean f(String s, int n)
We could represent the signature of this method as Str -> Int -> Boolean (Haskell) or Boolean -> Str -> Int (more consistent with the Java way but not so much with currying). Let's stick with Haskell for now.
What's nice about | Str -> Int -> Boolean | is that it gives you a good idea of what you get when you apply this function partially (which is at the heart of Haskell's approach): give it a Str, and you get an | Int -> Boolean |. Give it a Str and an Int and you get a Boolean.
This approach also solves the parameter positioning problem: now we just need to decide whether we want that parameter at the beginning or at the end:
| Str -> Int -> Boolean | f = ...;
if (f("foo", 42))
{
...
}
Or:
f : | Str -> Int -> Boolean | = ...;
By the way, the reason why Scala went with the latter approach is because it works better with type inference, which is something that might be relevant to Fan.
The downside to this notation is that it makes the Fan syntax a little less evolutionary and a little more revolutionary. It is my firm belief that a language can only become mainstream if it is an evolution over existing languages, and this might be the little bit of exoticism that will feel alien to enough people that the language won't become mainstream.
Anyway, just a few thoughts. I haven't researched this extensively and I don't expect to see such a radical change make it in the language so late in the game, but I thought it wouldn't hurt to share.
-- Cedric
brianWed 25 Jun 2008
By the way, the reason why Scala went with the latter approach is because it works better with type inference, which is something that might be relevant to Fan.
I think that class members - especially methods shouldn't ever use type inference. If everything can be inferred, then it makes it hard to trace back to actually find the type of something. As it stands right now you only need to trace back the expression of a local variable and you get a method or field with an explicit type signature. So I'm all for type inference inside a method, but not at the class level.
The Haskell looks like an elegant solution, but I'm not how sure that works in an OO language like Fan. We have to tie every OO method back to a function too. So in the end you want your functions to be modeled like methods - which are parameters and a return type.
tompalmerWed 25 Jun 2008
I agree with Cedric's original concern that single-param-functions are too different for most folks to swallow. Not even Scala does this.
I also agree that types after vars can fit better with type inference (and values are generally always on the right). I was heading that way with my own toy language. ("Toy" because I never finish my own side projects.)
(Side note: If I had succeeded on the function type syntax, my next target was Map type syntax to make it fit the same mold. I do reserve some comments.)
brianWed 25 Jun 2008
I agree with Cedric's original concern that single-param-functions are too different for most folks to swallow. Not even Scala does this.
Not sure I get this? You mean a function with a single parameter like this:
[0, 1, 2].each |Int i| { echo(i) }
heliumWed 25 Jun 2008
The type of
boolean f(String s, int n)
would more closly noted as
(String, Int) -> Bool
in Haskell.
String -> Int -> Bool
is a curried function. It's a function returning a function. -> is right associative. You read it as
String -> (Int -> Bool)
, a function expeting a string and returning a function. In Fan-syntax
|Int->Bool| f(String s)
Of course there is a function
curry :: ((a, b) -> c) -> a -> b -> c
that creates a curried version of an uncurried function. Equally there is a function that does the opposit
uncurry :: (a -> b -> c) -> (a, b) -> c
. (You can see from the type allown what it does.)
The type of curry is
c(b)(a)(c(a, b))
and the type of uncurry is
c(a, b)(c(b)(a))
in tompalmer's c-functionpointer-like notation, if that helps you (I can't read that, and I'm not even shour I didn't do any errors).
andyWed 25 Jun 2008
I agree the closure syntax is inconsistent with methods, though its never struck me as wrong (maybe I'm just used to it though). I'm open to suggestions, but I have yet to see anything that is better than the current syntax. I particularly do not like Scala's syntax.
tompalmerWed 25 Jun 2008
Actually, helium's examples don't get my proposed syntax right. It's less nesting than that.
I've made some examples below for the curry method. Lacking generics, all of A, B, and C would be Obj in Fan. But I'll use A, B, and C just for clarity here. Fancy code like this is also an example where generics are easier to read. Not saying anything needs to change. Just pointing it out.
// __Examples show first a method def then the function type of the method.__
// Current Fan.
|A -> |B -> C|| curry(|A, B -> C| function) {...}
||A, B -> C| -> |A -> |B -> C|||
// Somewhat Haskell-like.
A -> B -> C curry((A, B) -> C function) {...}
((A, B) -> C) -> A -> B -> C
// My proposed syntax.
C(B)(A) curry(C(A, B) function) {...}
C(B)(A)(C(A, B))
And for fun, my preferred map syntax (keeping things all in the same mold). I'm not totally convinced by this, but I think it's worth considering. The worst part is how no params implies a list. From this viewpoint, it becomes the odd duck, but I think it would be OK.
Int[Str] // vs. current Fan Str:Int
Int[][Str] // vs. current Fan Str:Int[]
I guess what's weird with types on the left is that you always get usage backwards from declarations. For instance:
C(B)(A) curriedFunction := curry(&someFunction)
a := someA
b := someB
c := curriedFunction(a)(b)
Note that the B and A are backwards from a and b. Really, types on the right (like Pascal, ActionScript, Scala, and so on) works best for a language generally. But it's just not familiar to people with a C background.
brianWed 25 Jun 2008
Int[Str] vs. current Fan Str:Int
This was the original design, but we hated it. The syntax V[K] mimics the accessor, but the K:V syntax mimics the declaration of maps, which to Andy and I was more intuitive.
As to the general discussion, I agree that methods aren't consistent with functions. But I start with the premise that methods should be C/Java/C# like, it ain't perfect, but Fan is a transitional language so I think it is the right move.
Then we picked what we thought was the best syntax for function types which had a matching syntax for closures (which was based on Ruby). We choose the return type at the end because it is better. Yes it is inconsistent, but it doesn't seem like such a big deal to me.
The syntax is not ideal for true functional programming, but I'm not sure that Fan is really suited to that anyhow. Fan is a baby step from OO to functional programming.
I did the currying only because it seems like a nice general purpose way to bind instance methods to functions for callbacks. As for functional guru stuff like currying/partial application - I'm no expert. As Helium as pointed out the term curry as used in Fan isn't technically correct.
What would really get me psyched is solving the grammar the problem to get rid of |,| without having the complicate with-blocks.
cbeust Tue 24 Jun 2008
The more I think about it, the more it seems to me that Fan's current signature notation is not consistent nor does it work well with Fan's currying.
About consistency:
1) Parameters are sometimes "inside" and sometimes "outside". I understand Brian's explanations and I think I should be able to write Fan code without making the mistakes I made recently, but I still think this could be simplified.
2) Declarations are "return type, parameter 1, parameter 2" while the closure syntax is "parameter 1, parameter 2 -> return type".
Since Fan supports currying, we should look at the return type as just another parameter, and after thinking about this more, I think that Haskell's notation might solve all these problems (note: I'm very much a beginner in Haskell, but at least that's one part that I think I understand).
For example, let's look at the following Java method:
We could represent the signature of this method as Str -> Int -> Boolean (Haskell) or Boolean -> Str -> Int (more consistent with the Java way but not so much with currying). Let's stick with Haskell for now.
What's nice about | Str -> Int -> Boolean | is that it gives you a good idea of what you get when you apply this function partially (which is at the heart of Haskell's approach): give it a Str, and you get an | Int -> Boolean |. Give it a Str and an Int and you get a Boolean.
This approach also solves the parameter positioning problem: now we just need to decide whether we want that parameter at the beginning or at the end:
Or:
By the way, the reason why Scala went with the latter approach is because it works better with type inference, which is something that might be relevant to Fan.
The downside to this notation is that it makes the Fan syntax a little less evolutionary and a little more revolutionary. It is my firm belief that a language can only become mainstream if it is an evolution over existing languages, and this might be the little bit of exoticism that will feel alien to enough people that the language won't become mainstream.
Anyway, just a few thoughts. I haven't researched this extensively and I don't expect to see such a radical change make it in the language so late in the game, but I thought it wouldn't hurt to share.
-- Cedric
brian Wed 25 Jun 2008
I think that class members - especially methods shouldn't ever use type inference. If everything can be inferred, then it makes it hard to trace back to actually find the type of something. As it stands right now you only need to trace back the expression of a local variable and you get a method or field with an explicit type signature. So I'm all for type inference inside a method, but not at the class level.
The Haskell looks like an elegant solution, but I'm not how sure that works in an OO language like Fan. We have to tie every OO method back to a function too. So in the end you want your functions to be modeled like methods - which are parameters and a return type.
tompalmer Wed 25 Jun 2008
I agree with Cedric's original concern that single-param-functions are too different for most folks to swallow. Not even Scala does this.
I also agree that types after vars can fit better with type inference (and values are generally always on the right). I was heading that way with my own toy language. ("Toy" because I never finish my own side projects.)
But more important than either, in my opinion, is consistency. That's why I argued for changing closure and function type syntax to make the return type always on the left. But I didn't win so far.
(Side note: If I had succeeded on the function type syntax, my next target was Map type syntax to make it fit the same mold. I do reserve some comments.)
brian Wed 25 Jun 2008
Not sure I get this? You mean a function with a single parameter like this:
helium Wed 25 Jun 2008
The type of
would more closly noted as
in Haskell.
is a curried function. It's a function returning a function. -> is right associative. You read it as
, a function expeting a string and returning a function. In Fan-syntax
Of course there is a function
that creates a curried version of an uncurried function. Equally there is a function that does the opposit
. (You can see from the type allown what it does.)
The type of curry is
and the type of uncurry is
in tompalmer's c-functionpointer-like notation, if that helps you (I can't read that, and I'm not even shour I didn't do any errors).
andy Wed 25 Jun 2008
I agree the closure syntax is inconsistent with methods, though its never struck me as wrong (maybe I'm just used to it though). I'm open to suggestions, but I have yet to see anything that is better than the current syntax. I particularly do not like Scala's syntax.
tompalmer Wed 25 Jun 2008
Actually, helium's examples don't get my proposed syntax right. It's less nesting than that.
I've made some examples below for the curry method. Lacking generics, all of
A
,B
, andC
would beObj
in Fan. But I'll useA
,B
, andC
just for clarity here. Fancy code like this is also an example where generics are easier to read. Not saying anything needs to change. Just pointing it out.And for fun, my preferred map syntax (keeping things all in the same mold). I'm not totally convinced by this, but I think it's worth considering. The worst part is how no params implies a list. From this viewpoint, it becomes the odd duck, but I think it would be OK.
Also, seems the D folks like this style of map type syntax.
tompalmer Wed 25 Jun 2008
I guess what's weird with types on the left is that you always get usage backwards from declarations. For instance:
Note that the
B
andA
are backwards froma
andb
. Really, types on the right (like Pascal, ActionScript, Scala, and so on) works best for a language generally. But it's just not familiar to people with a C background.brian Wed 25 Jun 2008
This was the original design, but we hated it. The syntax
V[K]
mimics the accessor, but theK:V
syntax mimics the declaration of maps, which to Andy and I was more intuitive.As to the general discussion, I agree that methods aren't consistent with functions. But I start with the premise that methods should be C/Java/C# like, it ain't perfect, but Fan is a transitional language so I think it is the right move.
Then we picked what we thought was the best syntax for function types which had a matching syntax for closures (which was based on Ruby). We choose the return type at the end because it is better. Yes it is inconsistent, but it doesn't seem like such a big deal to me.
The syntax is not ideal for true functional programming, but I'm not sure that Fan is really suited to that anyhow. Fan is a baby step from OO to functional programming.
I did the currying only because it seems like a nice general purpose way to bind instance methods to functions for callbacks. As for functional guru stuff like currying/partial application - I'm no expert. As Helium as pointed out the term curry as used in Fan isn't technically correct.
What would really get me psyched is solving the grammar the problem to get rid of
|,|
without having the complicate with-blocks.tompalmer Thu 26 Jun 2008
Thanks much for the history.