#564 Symbols

brian Fri 1 May 2009

This is an offshoot from the #550 Localization topic...

As I started thinking about the syntax sugar for looking up a localized value using a string key, I thought to myself that there is no reason that such keys shouldn't be statically checked by the compiler. This leads to notion of pod qualified string "keys" which can be referenced as some sort of symbol with compile time checking. That sorts of brings us back around to previous discussions regarding static type checks and namespace support for facets (see #252 and #324). I think there might be a nice unification opportunity here...

Symbol Class

Let's say we have a new first class notion of a symbol:

const class Symbol
{
  Pod pod()
  Str qname()
  Str name()
  Type of()
  Obj? defVal()  // original declared value
  Obj? val()     // may be overridden on a per project basis
  Obj? locale(Locale := cur)  // val by default, overridden on Locale basis
}

Symbol Literals

I can declare new symbols as top level grammar productions in any compilation unit. But by convention, a pod's symbols are defined in a source file named "symbols.fan":

// symbols for fwt pod...
Str okName := "OK"
Str cancelName := "Cancel"

Symbol values are required to follow existing facet value rules (must be serialized objects). One good way to think of symbols is "fields" on the pod itself.

I can declare a symbol literal using the @ prefix:

@fwt::okName   // fully qualified
@cancelName    // requires using fwt import

The literal resolves to a Symbol instance, which may then be used to access the meta-data, value, or localized value:

@okName.pod     // evals to Pod.find("fwt")
@okName.qname   // evals to "fwt::okName"
@okName.name    // evals to "okName"
@okName.val     // evals to "OK"
@okName.locale  // evals to "OK" for current locale

Reflection would support accessing all a pod's declared symbols:

class Pod
{
  Symbol? symbol(Str name, Bool checked := true)
  Symbol[] symbols()
}

We would enhance string interpolation to allow $@ to be used:

out.w("<h1>[email protected]</h1>")
out.w("<h1>$@acme::title.locale</h1>")

Localization

Symbol localization would be defined similar to today, although all the default values (most likely English) would be defined in "symbols.fan", and the locale files would typically just be used for language specific overrides. The methods Pod.get, Type.get, and Locale.get would be replaced with Symbol.locale.

Configuration Override

Symbols could be overridden on a per project basis, by declaring text files in under "{home}/symbols/".

For example let's say my pod declares a timeout symbol:

// myPod/src/symbols.fan
Duration timeout := 10sec

Then I might override that value for my installation:

// {home}/symbols/myPod.symbols
timeout = 60sec

Now the symbol evaluates as follows:

@timeout.val     =>  60sec
@timeout.defVal  =>  10sec

Once we have a more comprehensive solution for separating the core install from project based install, those files might move out from under the Fan home dir.

Facets

I think most everyone has championed the idea of statically defined facet names with namespace support. With this proposal, facet keys become Symbols instead of Strs. The compiler will ensure that the value type of the facet matches the Symbol.of declared type. The facet APIs for Pod/Type/Slot are modified as follows:

Symbol:Obj? facets()
Obj? facet(Symbol sym, Obj? def := sym.val) 

Note that facets now default to the symbol's default instead of null (unless a default is explicitly passed). The Type.facet method would still have its extra inherited param.

Fandocs

The list of symbols declared by a pod becomes a part of its publically exposed interface. As such symbols will need to be incorporated in the documentation generated by docCompiler.

Summary

I just kind of stumbled on this design today, so might be some holes I haven't considered. But overall I think it adds a powerful feature to Fan, but is general purpose enough to solve a lot of different problems in a unified manner.

tompalmer Fri 1 May 2009

So, not all symbols would be strings, then? Interesting.

Seems like some other interesting ideas here. I haven't thought deeply enough to give feedback on the specifics.

Anyway, you know my opinion on namespaced symbols (for others who haven't read the history, I'm super in favor), so I'll stay out of the discussion for now beyond that. Still, I'll see if I can help work out kinks that others bring up.

KevinKelley Fri 1 May 2009

I really like the sound of this. I've been frustrated recently by some things about how facets, and localization strings, work, and this appears to neatly answer all issues I've had.

andy Fri 1 May 2009

I need to spend some time grok all this but my two initial observations:

Symbol localization would be defined similar to today, although all the default values (most likely English) would be defined in "symbols.fan"

  1. That doesn't feel right - mixing my default localization in with symbol defs?
  2. Seems like symbols should just default to the string value if I have not declared a value for them (or did I miss that?).

brian Fri 1 May 2009

That doesn't feel right - mixing my default localization in with symbol defs?

The only real difference b/w how we do it today is that symbols.fan replaces en.props as the default values.

andy Fri 1 May 2009

I can see what you mean - though I think the current en.props was pretty heavily favored towards localization. Where the symbol proposal is much more a configuration mechanism. So it "feels" messier to me.

brian Fri 1 May 2009

Where the symbol proposal is much more a configuration mechanism.

The difference between localization and configuration isn't black and white - it is more of a spectrum. The cool thing about the unified approach is I can tweak things like accelerators, fonts, colors, etc using the same techniques I would tweak localization (without having to change my language).

Also, I think this model is better because I actually declare serialized objects:

@cutName := "Cut"
@cutIcon := `fan:/sys/pod/icons/x16/cut.png`
@cutAccelerator := Accelerator("Ctrl+X")

That means that symbol literals are brought in using static typing:

acc := @cutAccelerator  // typed as Accelerator

andy Fri 1 May 2009

Yeah I suppose that's true. And static typing would be nice there. I think I can get on board with that. I still think that symbols should default to the string value though if not defined - then you can easily use them in maps and stuff like that.

cheeser Fri 1 May 2009

So what would it look like to provide locale specific values for these symbols? And would the compiler guarantee that the localized overrides are at most a subset of the defined symbols? i.e., if a symbol goes away in the default list the compiler will bark at you if you a locale override still has the old symbol listed...

jodastephen Fri 1 May 2009

I've read this proposal three times now, and I've been happy each time I've read it. It is simple, understandable and very powerful.

It gives us compile-time checked keys (I often use a Java interface to define constants for these, so its not much different to that conceptually).

But it also gives us type-safety for the values. I have a whole Java framework at present that converts XML configuration files to objects of specific types to reduce the load in the main code. This proposal would eliminate the need for that in Fan as each symbol's value would be correctly typed. (One thing to take care of is that non-const symbol values would need to be cloned before being returned)

One good way to think of symbols is "fields" on the pod itself.

Random idea - maybe we should actually define them that way with a new pod construct?

pod gfx {
  Str cutName := "Cut"
  Icon cutIcon := `fan:/sys/pod/icons/x16/cut.png`
  Accelerator cutAccelerator := Accelerator("Ctrl+X")
}

Although I can't immediately think of a need, this would allow for the addition of pod scoped methods - gfx::methodName() - and maybe direct overriding/initialisation of the build system.

.

A separate point that might be worth considering is whether language overrides could have an optimised creation format in certain circumstances:

// symbols.fan
Accelerator cutAccelerator := Accelerator("Ctrl+X")
// fr.props
cutAccelerator = Accelerator("Ctrl+D")  // proposed above
cutAccelerator = "Ctrl+D"  // possible shortcut?

ie. that the override looks to use the fromStr(Str) constructor if it is available.

.

Also, is there any reason why symbols have to be defined at the Pod level? Could we have Class level symbols (I haven't thought much about this). It might be useful for some of what I do just to scope the symbol more tightly. (maybe just as a way of prefixing the symbol key - @classDefinedKey is the same as @classThatDefinedIt.classDefinedKey)

brian Sat 2 May 2009

So what would it look like to provide locale specific values for these symbols?

I think both configuration overrides and localization overrides would use a new file called a "symbols" file. This would basically be a cross between a props file and a serialized object, in that it is a list of name/serialized objects pairs.

symbol goes away in the default list the compiler will bark at you if you a locale override still has the old symbol listed

This sort of thing would be pretty easy to implement since everything is fully reflective. I am not sure that the compiler should do that on every run (maybe another tool or switch).

Random idea - maybe we should actually define them that way with a new pod construct?

To me this is implied by the top level of every source file. For example this is how I visualize things in my mind:

pod foo
{
  // symbols.fan
  Str ok := "ok"
  Str cancel := "cancel"

  // A.fan
  class A {...}
}

A separate point that might be worth considering is whether language overrides could have an optimised creation format in certain circumstances

That is interesting. Although I think if we do it, then it should be a generic feature. Basically it is just a form of sugar using type inference of the LHS. But I need to give that some thought.

One question is should we force you to type the symbols (as we do fields), or allow inference (as we do local variables)? Since it is going to be disallowed to define symbols in terms of other symbols, type inference would be pretty easy to implement:

cutName := "Cut"
cutIcon := Image(`fan:/sys/pod/icons/x16/cut.png`)
cutAccelerator := Accelerator("Ctrl+X")

I lean to allowing type inference.

Also, is there any reason why symbols have to be defined at the Pod level? Could we have Class level symbols (I haven't thought much about this).

I think we should start simple. Having a fixed, simple naming structure is what gives Fan its meta-programming capabilities.

tompalmer Sat 2 May 2009

I think we should start simple. Having a fixed, simple naming structure is what gives Fan its meta-programming capabilities.

Stephen's comments made me wonder about the relationship to static consts (and methods) in classes. Are we just defining pod-level consts with the new system? Is special syntax needed? (Well, for facets still, I guess.)

Hmm. The new Symbol class (and its instances wrapping the seemingly assigned value) and the overriding concept are definitely beyond just simple consts.

tompalmer Sat 2 May 2009

Note that facets now default to the symbol's default instead of null

So, if we wanted a facet to default to null, we'd define a symbol like so?

Str? author := null

Well, I'd personally probably opt for an empty string in this case (well, I almost always prefer an empty string to null):

author := ""

Just trying to make sure I understand the issues. Would we just use comments to group localization symbols vs. facet symbols or other?

# Localization
title := "My Cool App"
something := "something"

# Facets
author := ""
priority := 0

Or whatever. Just trying to think about it.

jodastephen Sat 2 May 2009

I lean to allowing type inference.

That seems to make sense here.

brian Sun 3 May 2009

So, if we wanted a facet to default to null, we'd define a symbol like so?

Exactly.

Just trying to make sure I understand the issues. Would we just use comments to group localization symbols vs. facet symbols or other?

Probably the best way to annotate the symbols you expect a translator to override is by facets. But I am not so sure how that will work out in practice. For example, by convention we stick display name, bubble help, accelerator and icon all together in localization. But you wouldn't override the accelerator and icon in most cases. So I am not sure we really need to go that far (at least for now). So maybe comments are the way to go.

I guess that brings up a point, should symbols have facets? Kind of a weird catch-22 in the case facets are defined in terms of symbols. I'd say for now, symbols don't have facets. We can always add them in the future.

tompalmer Mon 4 May 2009

I guess that brings up a point, should symbols have facets?

Annotation definitions may be annotated in Java. I think there's value in it, personally. Even if you don't support it right away, I think it would be good not to block off the option.

brian Fri 8 May 2009

Promoted to ticket #564 and assigned to brian

Sounds like we have agreement that this is the way to go.

alexlamsl Fri 8 May 2009

Going with the annotation analogy - one thing that might be interesting to get is a Symbols Processing Tool as a plug-in to the Fan Compiler.

tompalmer Fri 8 May 2009

Glad to hear the news. I'll be curious to try it out once done.

brian Thu 14 May 2009

I started this feature this morning, then sort of hit a serious mental roadblock of self doubting. I've been wrestling with the difference between a symbol and a facet. They are both name/value pairs. But under my proposed design they are two different concepts where a facet is keyed by a symbol. Symbols values can be overridden by app or locale, facets can't (at least not in the same manner). So I've been asking myself should these concepts be unified in some way? And if so, how the hell would I do it?

Facets are name/value pairs associated with a pod, type, or slot. The key problem is that the name should be qualified and ideally the value should be typed. That means somewhere in the qualifying pod a declaration is needed. As an example: the sys::transient identifier is used for facets on fields. What is that definition of transient itself? Is it just a facet on the pod level? If so what does @transient really reference? Is there some syntax like someField@transient to read the facet value for a some field? That sort of leads to a multi-dimensional namespace.

Anyways, I didn't really come up with any good answers how to unify these two concepts into one concept. So I am going to stick to the proposed design, where symbol is really just the key in a facets map.

What I did decide was that pods shouldn't have facets (specified in build.fan today), they only have symbols. At the pod level, the difference between the two is fuzzy enough that it doesn't make sense to have to both.

I also debated about some top level "pod class" - I am not sure we need to go there right now. But I did decide I am going to require all of a pod's symbols to be declared in a file called "symbols.fan" so we can keep our options open.

JohnDG Thu 14 May 2009

I never replied to this thread because the concept needed more baking, in my opinion, before being implemented. Do you need this feature to be implemented now, or can you defer for some time?

brian Thu 14 May 2009

Do you need this feature to be implemented now, or can you defer for some time?

It is currently the "most breaking" change we out have outstanding, so I'd like to tackle it this week, or next week at the latest.

brian Sat 11 Jul 2009

I am planning on proceeding with this work as part of the Fan Repo work.

Symbols will be defined in a special "symbols.fan" file which uses a grammar of name/object pairs and an explicit or inferred type:

Str hello := "hello"
timeout := 10sec

I will support facets on symbols:

@option homePage := `http://localhost:8080/`

Facets will be reworked to use symbols under the original proposal.

Symbols becomes the standard way to do localization and configuration. All props and fog files will be replaced with a new single file format called "fan object map" with the "fom" extension. This will be a simple format defined as name/object pairs:

hello="hi there!"
timeout=30sec
homePage=`http://google.com/`

Note the "symbols.fan" file is used to define symbols and their default values. And fom files are used to configure/override symbols using the repo design. The repo design will allow overrides on a per symbol basis (instead of what I proposed with props).

I think the combination of symbols and repos will be provide a powerful unified mechanism to deal with things like localization, configuration, environment, etc in a type safe way.

Further comments on this proposal?

andy Sun 12 Jul 2009

So if I understand this all correctly:

  1. Symbols are simple typed name/value pairs defined in "symbols.fan"
  2. Symbols are now used for all facet,location, etc.
  3. Any symbol can now be used as a facet
  4. Symbol must be defined before its used in Fan source
  5. I can annotate a new symbol with an existing symbol
  6. I can override existing symbols using "symbols.fom"
  7. I can override existing symbols on a per-language basis using "locale/fr.fom"

Is that accurate?

brian Sun 12 Jul 2009

Is that accurate?

yeap

btw - I don't particularly love "fom" so alternate suggestions would be great

cheeser Mon 13 Jul 2009

If I were to want to use Java annotations then, could I just say something like:

using [java] javax.blahblahblah

in symbols.fan? I know you haven't quite figured that part out yet, but wanted to toss that in the mix since you asked. :)

So some options I see under this proposal.

  1. we just have that using clause like above. Might be overkill as some of the java annotations can be quite numerous.
  2. we combine that with a named symbol and an explicit java annotation name. This could be overly verbose but perhaps a bit safer wrt name collisions: get=javax.ws.rs.GET

Those are just some basic ideas after a first glance. What do you think?

brian Mon 13 Jul 2009

I just want to let everyone know that I am planning on starting the symbol and repo work this morning. I will do some of the core symbol work first. The symbol proposal is from May, but I realize that wasn't a lot of time for reviewing the repo design.

So if you have comments please let me know, or join the fandev IRC channel to talk about it.

brian Mon 13 Jul 2009

As part of this change, we will need a new operator for field storage to avoid conflicts with the @ character. The most obvious symbol is & which I don't believe will conflict with curry since one operates on fields and the other on methods/functions - although if we allowed you to "curry" a field's getter as a function that might be bad.

Suggestions?

brian Mon 13 Jul 2009

I am going to change the field storage prefix operator from @ to *. This will free up @ to use for symbol literals. I picked * to mimic C pointer dereference operator (which in my mind seems pretty close to Fan's storage operator).

brian Mon 13 Jul 2009

I am going to change the field storage prefix operator from @ to *. This will free up @ to use for symbol literals.

I have pushed this change. This operator isn't used a whole lot, but if you are working off tip you might need to change your code. Because * is also a binary operator you might need to add a semicolon in ambiguous cases (I ran across that in a couple of places).

tompalmer Mon 13 Jul 2009

Glad to hear of progress on this front. I have no particular recommendations at the moment. Haven't reviewed in detail but seems okay at first glance.

qualidafial Mon 13 Jul 2009

Why not allow symbols to be defined in any .fan file, e.g. alongside related classes? The compiler could always report an error if the same symbol is declared in more than one .fan file. Then you get to keep the symbol declaration close to its friends.

We also had a long discussion on #fandev in IRC relating to the default value of facets e.g.:

sys pod:
  @simple := true
  @serializable := true

my pod:
  @serializable class Person { ... }
  Person#.facet(@serializable) => true
  Person#.facet(@simple)       => true ..wait, what?

This is due to signature Type.facet(Symbol symbol, Obj? defVal = symbol.defVal). The consensus seemed to be that we should change the defVal argument to null. Likewise for slots. In this way, the default value is only applied if the symbol is actually declared on the class/slot in question.

brian Tue 14 Jul 2009

Why not allow symbols to be defined in any .fan file, e.g. alongside related classes?

That was my original plan - they could appear as as top level production in any compilation unit. But I figure it is better to start off strict, then change our minds later. This way we can change our minds and it is a safe non-breaking change. So like most things, I think we should start off with the most restrictive design for now.

This is due to signature Type.facet(Symbol symbol, Obj? defVal = symbol.defVal). The consensus seemed to be that we should change the defVal argument to null.

Agreed.

andy Tue 14 Jul 2009

I think we should start off with the most restrictive design for now

Agreed.

brian Tue 14 Jul 2009

As part of this design I want to move pod level meta-data out of build.fan so that is easily available for IDEs and other tooling.

I propose a new source file required in every pod called "pod.fan" which is strictly a list of facets. In the future we might enhance this format.

To clarify:

  • "symbols.fan": defines new symbols scoped with the pod, some of these symbols will be facet definitions
  • "pod.fan": defines pod level facets keyed by existing symbols using same syntax as type and slot facets

Let's take an example for the build pod:

// pod.fan
@podName="build"
@podDepends=[Depend("sys 1.0")]
@podDescription="Fan build utility"
@build::srcDirs=[`fan/`, `fan/tasks/`]
@build::includeSrc=true


// symbols.fan
** Facet definition for source dirs
@facet=FacetMode.pod 
Uri[]? srcDirs

** Facet definition for including source code in pod
@facet=FacetMode.pod 
Bool includeSrc := false

** Location of JDK configured in /etc/build/pod.fansym
Uri? jdkHome

KevinKelley Tue 14 Jul 2009

It still needs to be in an enclosing directory, like the build.fan is, right? Since it's got the build info like srcDirs. Will srcDirs be required to be relative to the location of this pod.fan file? That sounds to me like a reasonable simplifying assumption.

There needs to be something here, external to the actual source code, that's an analog to makefiles or to Visual Studio's project/solution files. I like the facets for build info, as opposed to initializing a subclass's fields.

I'm not sure I get the reasoning for having two separate files, though.

I take it the idea is that the pod.fan file is the required pod-build config information, and the build.fan is one possible way to build a pod (using the commandline tools). I'm not quite getting the distinction with pod.fan and symbols.fan, though.

Aside from me not getting that, I'm much in favor of putting build info into a known location, in a standard form, so it can be easily got to by tools.

tompalmer Tue 14 Jul 2009

As part of this design I want to move pod level meta-data out of build.fan so that is easily available for IDEs and other tooling.

I like that. I like having the metadata separate from the executable script.

I propose a new source file required in every pod called "pod.fan" which is strictly a list of facets.

It seems like an alternative would be to have a fog (or is that now fom) file with raw data in it. Well, I guess the facet map allows arbitrary keys from different namespaces more easily than would be possible with a strictly typed object. And having facets on pods (for runtime introspection) does make sense.

As to specifics, does it make sense to have things like source dirs available as a runtime pod facet? Maybe something like that would best be kept in build.fan? But easier for IDEs, I guess, and too many files could get convoluted. Hrmm. Just thinking out loud a bit right now.

tompalmer Tue 14 Jul 2009

I'm not sure I get the reasoning for having two separate files, though.

The "symbols.fan" file is for the purposes described previously: declaring symbols. The "pod.fan" file is for annotating pods themselves, such as package-info.java in Java. You could possibly put them together with a pod syntax as proposed earlier by jodastephen. That is, it would provide a scope for the symbols defined by a pod, separate from the facets on that pod. I'm not sure I have a full opinion on that.

andy Wed 15 Jul 2009

I can declare new symbols as top level grammar productions in any compilation unit. But by convention, a pod's symbols are defined in a source file named "symbols.fan"

...

That was my original plan - they could appear as as top level production in any compilation unit. But I figure it is better to start off strict, then change our minds later

I missed this my first go around. I have always assumed all symbols would be defined in a single well-known declarative file. Since otherwise they look like global variables but do not behave that way (i.e. not actually accessible). Seems very confusing. I figured the "fom" format would be used here as well. In any event, using the .fan extension seems wrong since its not true valid Fan source code.

I propose a new source file required in every pod called "pod.fan" which is strictly a list of facets. In the future we might enhance this format.

Chewed on this for a short bit, and I agree some should be pulled out (but not everything). And I think this is what the pod grammar would be useful for (by convention declared in pod.fan):

@someFacet=true
pod build
{
  description = "Fan build utility"    
  depends     = [Depends("sys 1.0")]
}

But I don't feel like build-time information should get pulled in here. That seems better suited for the existing build file.

brian Wed 15 Jul 2009

Andy and I talked this morning, and I think we settled on a design that collapses pod facets and symbols into a single "pod.fan" file that looks like this:

**
** Fan build utility
**
@depends=[Depend("sys 1.0")]
@build::srcDirs=[`fan/`, `fan/tasks/`]
@build::includeSrc=true
pod build
{
  ** Facet symbol definition for source dirs
  @facet=FacetPolicy.pod 
  @retention=RetentionPolicy.source
  Uri[]? srcDirs

  ** Facet symbol  definition for including source code in pod
  @facet=FacetPolicy.pod 
  @retention=RetentionPolicy.source
  Bool includeSrc := false

  ** Symbol for location of JDK configured in /etc/build/pod.fansym
  Uri? jdkHome
}

In this design the pod is declared similar to a class definition with a consistent syntax. The "fields" of the pods are the symbol definitions:

** fandoc
@facets
pod <podName>
{
  <symbolName> := <symbolVal>
  ...
}

The description of the pod becomes the first sentence in its fandoc (just like type and slot descriptions).

With this new design the 90% case is that all the information needed to build the pod is located in "pod.fan", so there is no need to have a "build.fan" script. To avoid creating unnecessary files, we will create a new command line tool "fanb" which is used to invoke a build from either a "pod.fan" or a build script. During the repo work I will also encapsulate the convoluted logic for bootstrap builds into this tool to avoid the nightmare we have today.

You guys like that design?

KevinKelley Wed 15 Jul 2009

+1

tompalmer Wed 15 Jul 2009

Pros and cons, but overall, I like it. Glad to see those RetentionPolicy.source facets there.

Side question, I understand that the @build::srcDirs facet above includes the pod name for clarity here, but it wouldn't be necessary without name collisions, right? That is, I presume @srcDirs would work equally well in the pod build or if using build?

brian Thu 16 Jul 2009

Side question, I understand that the @build::srcDirs facet above includes the pod name for clarity here, but it wouldn't be necessary without name collisions, right?

correct - just an example, in this case the symbols from build would already be in scope (or you would likely import them via a using statement)

any last comments on this design?

brian Thu 16 Jul 2009

From now until next build you will not be able bootstrap compile from the hg tip.

The last changeset which should work with bootstrap is: http://hg.fandev.org/repos/fan-1.0?cs=b040618d9935

If you really want to work of tip, then email me and I can email you the temp builds I am using for bootstrap.

jodastephen Sun 19 Jul 2009

What about allowing classes to be embedded in pods?

pod foo {
  mySymbol := "Hello World"

  class MyMain {
    static const name := "Stephen"
    ...
  }
}

(This would be optional, but would allow for rapidly setting up a pod for tuition purposes)

brian Sun 19 Jul 2009

This would be optional, but would allow for rapidly setting up a pod for tuition purposes

I thought about - although I think the type would be outside the pod braces like we do other types. I updated hello world with pod to use two different files since I think that should be the convention. My instinct is to keep it restrictive now, and disallow type definitions in the "pod.fan" file. We can always relax the rule later.

brian Tue 21 Jul 2009

One thing I am going to do is use the "virtual" keyword with symbols. Non-virtual symbols are final and cannot be overridden in a configuration file. Virtual symbols can be overridden in a etc config file.

Or as an alternative we could say symbols are virtual by default and use the "final" keyword. But my gut says virtual should be explicitly specified.

jodastephen Tue 21 Jul 2009

I've always preferred the Java style of virtual by default and specifying final (in general, and especially in this case). I think its more friendly and more amenable to growth. I also think that virtual by default suits Fan better, as a complement to Fan's relatively simple type system.

brian Tue 21 Jul 2009

I've already made it non-virtual by default, but once you guys get this next build and play around it and a majority of you want to flip it, that shouldn't be a big deal.

I've got most of all this symbol and repo stuff working but not all the details polished out. I am going to try and do a build so those who care can play with it and then I will do another build once I've got some feedback.

Also one of the things that might be a little controversial:

// consider a symbol definition like this:
foo := 4

// consider a symbol literal 
@foo  => evaluates to optimized version of Symbol.find("pod::foo")
@foo.val => evaluates to Symbol.find("pod::foo").val

In that last case we actually know that @foo.val is an Int - so I'm thinking I am going to add an implicit cast so you can actually use it as its correct type and get type checking:

x := @foo.val  // x is typed as Int

Techinically that is best handled with true parameterized types, but in this case I think a special case makes it totally worth it.

KevinKelley Tue 21 Jul 2009

might be a little controversial:

Not with me; I'm finding myself to be a real big fan of type-inferencing everywhere.

Dynamically typed languages let you quickly rework your designs, since you don't have to revisit every use of a type to change declarations, every time it changes. But, you pay by not finding out until runtime where are the things you forgot to make consistent.

Static typing lets you depend on the compiler to ensure the consistency: move a method and the compiler makes you visit every use of it.

Fan's type inference lets me get the best of both worlds: it's kind of like the typespec is a control point, analogous to a graphics program letting you specify a complex curve with a few control points: change the type spec in one spot, and the inferred types of derived variables follows, and everything works except where you ought to have to think about it.

My feeling so far is, the more inferencing the better. You can always specify types to lock down a control point; better inferencing means you need fewer of such control points to lock down a statically-correct design; which in turn means it's easier to vary the design.

tompalmer Wed 22 Jul 2009

I think the cheating inference is fine, too.

The most unintuitive part for me is that I'd expect @foo to give me a value rather than needing to say @foo.val. Especially since we already said foo := 4 and all. I get reasoning (having been partially motivated by me in the first place), but it's still hard for me to keep it correctly in my head all the time.

tompalmer Wed 22 Jul 2009

Maybe pod::foo or maybe even just foo (if using the pod) could mean the same thing as @foo.val. I guess then @foo would be the symbol equivalent of Type# for types. Just trying to think about it. Maybe not a good idea. Just trying to latch onto an intuitive feel for it.

brian Wed 22 Jul 2009

I guess then @foo would be the symbol equivalent of Type# for types.

The literal @foo works like a Type or Slot literal - it evaluates to the Symbol. Then you can call methods on the symbols from there. This is actually what you want when using symbols as facet keys:

isSerializable := t.facet(@serializable) == true

Here is some fansh calls on a symbol literal:

fansh> @serializable.qname
sys::serializable
fansh> @serializable.name
serializable
fansh> @serializable.pod
sys
fansh> @serializable.of
sys::Bool
fansh> @serializable.val
false

JohnDG Wed 22 Jul 2009

Especially since we already said foo := 4 and all

I really don't like the overloaded assignment operator for symbols. Should be more something like, foo := Symbol { val = 4 }, or perhaps a literal syntax, foo := @@4, where @@ creates a symbol with the specified value.

Otherwise, it's very confusing that @foo := 4, but @foo != 4. Violates the principle of least surprise, as well as basic logic ingrained into everyone.

tompalmer Wed 22 Jul 2009

The literal @foo works like a Type or Slot literal - it evaluates to the Symbol.

Understood. I just need things to settle correctly in my mind. I think this explicit comparison might be valuable in the symbol documentation when that's done. And maybe emphasize the slot literal part since # is used as a prefix (or infix depending on the context, I guess) there.

brian Wed 22 Jul 2009

Otherwise, it's very confusing that @foo := 4, but @foo != 4. Violates the principle of least surprise, as well as basic logic ingrained into everyone.

It is pretty consistent with fields:

pod foo { Int f := 4 }
class Foo { static const Int f := 4 }

#f   =>  Field
@f   =>  Symbol

#f.get(null)  =>  4
@f.val        =>  4

JohnDG Wed 22 Jul 2009

But fields is more consistent with logic:

class Foo { Int f := 4 }
...
f == 4 // true
pod Foo { Int f := 4 }
...
f == 4 // ERROR
@f == 4 // false

jodastephen Wed 22 Jul 2009

This does look rather confusing. Wouldn't this work better?:

pod foo { Int f := 4 }
class Foo { static const Int f := 4 }

f    =>  value of Field
@f   =>  value of Symbol
#f   =>  Field
#@f  =>  Symbol

Or should we be asking the deeper question as to what the difference is between a pod field, a static field and an instance field?

brian Wed 22 Jul 2009

It is done now, so a bit late. Although if we really want to change it obviously we can do that.

Symbols are kind of like fields on the pods, but after a lot of thinking I've decided they are definitely their own abstraction. There really is no perfect consistency with fields because they aren't fields.

What they are is really symbols used as keys that also happen to have a value. I think @foo is consistently used from that viewpoint:

// as key of a facet symbol/value pair
@foo="x" class AsFacet {}

// as key to lookup facet
AsFacet#.facet(@foo)

I think about them much like :foo symbols in Ruby. That is why I think about them more like #field literals.

tompalmer Wed 22 Jul 2009

I like this description, actually:

#f   =>  Field
@f   =>  Symbol

#f.get(null)  =>  4
@f.val        =>  4

I think that makes it clear enough, so I'm down with it. Just the facet syntax of @foo="x" seems a bit misleading at this point, but I think it's still learnable.

Again, maybe direct access without the @ prefix could give a value (as in, sym vs. @sym.val just like field vs. #field.get(null)). It makes the relationship story more complete, but the "globals" feel might cause trouble. Maybe something to consider in the future.

qualidafial Wed 22 Jul 2009

Just the facet syntax of @foo="x" seems a bit misleading at this point, but I think it's still learnable.

I'm starting to feel that using symbols for configuration, internationalization and for facets is maybe overloading too many different concepts onto the same construct.

Consider that with facets, symbols are used as keys, but in internationalization and configuration they are used as key (symbol literal) and value (value.val, .locale, .defVal, etc).

I will try to find more time to elaborate later tonight if possible.

brian Thu 23 Jul 2009

I'm starting to feel that using symbols for configuration, internationalization and for facets is maybe overloading too many different concepts onto the same construct.

Yes, at first I struggled with this which is why I sat on the feature for several months. But I gave it a lot of thought and although it may be not perfect it is a huge leap forward. Sure it is an overloaded concept, but so are classes and objects in general. What symbols boil down to are:

  • singleton objects of type Symbol
  • easily referenced within namespace with @ operator
  • reflective value type
  • configurable value

Symbols have removed huge swathes of code that were all doing similar things, but slightly different. So now that I have it working, I am feeling quite comfortable that this is a really good direction.

We might still need some tweaks - especially with regard to easy use of localization (I haven't done that yet).

I have almost all the basics of symbols and repos working today. I would really love some people to play with it and see what they think (before the official build). This is code you really have to play with to see how it all works together. If you have the time and inclination, please email me and I will email you a bootstrap build you can use to build from hg tip.

qualidafial Thu 23 Jul 2009

I think my primary concern is that a Symbol is both a key and a value, depending on the context. I love having symbols as keys for facets, and the type safety that this adds.

@serializable="blah" => compiler error, should be Bool

I also like that symbols can be assigned a default value, and that by omitting the value assignment a facet adopts that default value. Thus the following two statements are equivalent (right?).

@serializable class A {}
@serializable=true class A {}

What bugs me is that neither locale() nor val() are in any way related to facets. In fact, locale() and val() are not even related to each other. So even though symbols are this cool catch-all facility for type safe, keyed lookup with compiler verification of keys--each client (facets, localization, pod configuration) is using symbols a little differently, and so Symbol ends up receiving extra API to accommodate each client. This smells funny to me.

I think that with some more work there could be further unification which would make the usage identical or at least very similar in each case. In my mind the best citizen so far is facets, which use Symbol solely as a key with type safety and a default value. I would prefer to leave out Symbol.val() in favor of Pod.symbol(Symbol), and leave out Symbol.locale(Locale) in favor of Locale.symbol(Symbol).

One question I have: do symbols get declared in special places based on their usage? That is:

  • Is a facet symbol declared in a special file only for facets? Is the virtual concept applicable to these symbols?
  • Is a configuration symbol declared in a special file only for configuration symbols? Are these symbols implied virtual?
  • Do localizable symbols have to be declared in a special localized files? Are these symbols implied virtual?

Maybe these questions have been answered but the thread is so long now that I've lost track of what decisions have been taken.

helium Thu 23 Jul 2009

I'm starting to feel that using symbols for configuration, internationalization and for facets is maybe overloading too many different concepts onto the same construct.

Yep, definitely.

Before symbols felt similar to strings that were used as key in a special dictionary (facets), now they can additionlly have values themself that you can use for configuration.

brian Thu 23 Jul 2009

One question I have: do symbols get declared in special places based on their usage?

No - symbols are all declared in "pod.fan". Any additional meta-data (like facet, etc) would itself be configured facets (although I haven't done that yet, and probably won't for now).

Having something like Pod.config(Symbol) and Locale.val(Symbol) conceptually is quite clean, and I like that. However I definitely wouldn't want the verbosity of Pod.config(@foo) versus just @foo.val. I like the direction, but I think the notion of getting the symbol value needs to be as short and sweet as the key. If you look at Stephen's post he suggested:

#@foo  =>  Symbol key
@foo   =>  Symbol val

I like that direction too (maybe a bit out of control with symbols). Although technically facets should be #@foo=val then since they are a key/val pair.

Like I said before, I recommend that you actually play with the code to really get your head about it.

qualidafial Thu 23 Jul 2009

I like the name Pod.config, could we change Symbol.val to Symbol.config to match? I'm imagining someone naively calling @foo.val when what they really want is Bar#.facet(@foo). If we rename the val method to config then this confusion (mostly) goes away I think.

Thus in Symbol.fan:

Obj? config() { return pod.config(this) }
Obj? locale(Locale locale := Locale.current) { return locale.val(this) }

I could get on board with this.

brian Thu 23 Jul 2009

I prefer a single val method for whatever the value is (and treat config/locale the same way). But I am not going to do locale until after this build and everyone has time to play it (haven't really given a lot of thought to how it should work).

I have all the repo and symbol stuff pretty much done (for what I plan on doing for this pass). Soon as I find time to update the docs I will post a build.

I can tweak the syntax and model fairly easy now. The big, huge changes like build script changes, "pod.fan", fcode format changes, LoadSymbol opcode, repo searching, etc all in place now.

brian Fri 24 Jul 2009

Ticket resolved in 1.0.45

Login or Signup to reply.