Found it! (Somehow the good ideas always come after asking others for help.) I forgot to make the MyClass constructor public. I'm still curious to learn what this error message means, or why F# is looking for the backquote-number class.

By on 7/27/2009 1:53 PM ()

Names like

TypeName`N

are the way the CLR represents

TypeName<Arg1,Arg,...ArgN>

under the hood. You'll see these kinds of names exposed through a number of APIs and diagnostics throughout the .Net platform, so it's useful to know that it just means 'the generic version of that type that takes N generic parameters'.

By on 7/27/2009 1:56 PM ()

And I should note that you'll live a longer, happier life if you write

1
2
3
4
type t(a,b) =   
    inherit MyClass<string,int>(a,b)
    member x.SayHello =
       System.Console.WriteLine("Hello!")

instead. Avoid the form of classes that lack constructor parens right after the type declaration and that use explicit fields.

By on 7/27/2009 2:02 PM ()

[I apologize for any mis-understandings below -- even RTFM isn't very explict about these issues. And when I refer to "I", I also mean "someone reading the code" that may not have been the author.]

> Avoid the form of classes that lack constructor parens right after the type declaration

The problem I have with this is that (at least at the moment) I'm not sure what the object footprint is that I'm actually creating. In:

1
2
3
type t(a:int,b:int) =   
    member x.SayHello =
       System.Console.WriteLine("Hello!")

Because "a" and "b" are not used anywhere, are "a" and "b" actually part of the object or not?
Or are they just initalizers for the default c'tor? I'm not even sure what the "spirit" of this construct is -- from the "lightweight object" perspective (like f#/ocaml Records), I'd expect "a" and "b" to be part of the object, but from an .Net perspective, I would not, and I'd expect "a" and "b" to be just "optimized-away" in the above case. (There is no compiler warning to suggest that "a" and "b" are never referenced and are therefore being thrown away.) I'm pretty sure that at the moment, these are "real" object slots, but in the future, could they be optimized-away? (Would need to know if I were going to use reflection, etc.)

The documentation's explanation of "a" and "b":
The <i>parameter-list<i> describes constructor parameters Leads one to the wrong conclusion - they are not simply c'tor parameters, but member slot declarations. Now if I have: <code lang=fsharp>type t(a,b) = member x.SayHello = System.Console.WriteLine("Hello!" + a.ToString() )</code> I know that "a" is part of the object, given that it compiles and there's no other "a" hanging around. With: <code lang=fsharp>type t(a,b) = inherit MyClass<string,int>(a,b) let aa = a member x.SayHello = System.Console.WriteLine("Hello!" + aa.ToString() )</code> It's again a little confusing, because it looks like "aa" should be "closed over" the member function, but becuase the member function declaration isn't a functional construct, I'm again not sure what is going on. Is "aa" a slot or an alias for "a"? If it's the former, seeing neither is mutable, I'm really wasting 4 bytes, and again, there's no compiler warning that says "warning: slots 'a' and 'aa' will always refer the the value of 'a' at the time of construction (barring use of reflection)" -- could the compiler throw one of these slots away and simply make "aa" an alias for "a"? Over and over the f# tutorials tell the new user that "In let x', 'x' isn't a variable, it's just a binding!" - but here we've allocated storage - I'm not sure what the rational was for overloading "let" in this way, when they could have just allowed "vals" to be initalized. (Though I'm sure there was a good reason.) (Maybe just being explicit and saying "slot" or "obj"?) Now, if I hover over these in the editor, they both do say "val" -- so it would seem I have a hint that they are both slots. But consider: <code lang=fsharp>type t(a:string) = let aa = a member x.SayHello = let aaa = aa System.Console.WriteLine("Hello!" + aaa.ToString() )</code> Now "aaa" is still id still "val" in the hover, but we don't have an object slot here, just a local binding, so the tip wasn't really that helpful. (From a type-system perspective they are all "val:string", but "aaa" is a very different thing from "a" and "aa" - again "val" seems like a strange choice to create and name an object slot.) Identifier syntax coloring based on slot vs. let-binding could be helpful here. Although I appreciate brevity, at least if I see "val" I know there's a there there... thanks

By on 7/27/2009 5:14 PM ()

A few quick notes.

All 'let' bindings say 'val' in the tooltips. 'val' has nothing to do with 'field' in this context, that's just what tooltips display for showing the types of identifiers.

The F# spec here about 'what becomes fields' is subject to change up until VS2010 releases. The spec is also thin in describing this (and lots of other stuff; we have a lot of spec work to do).

If you really care about 'what becomes a field', then you may prefer the 'explicit' syntax with val. But 99% of the time I think you ought not care (only if you're very deep into perf, in which case you maybe should be using structs instead of classes anyway).

By on 7/28/2009 1:56 PM ()

> All 'let' bindings say 'val' in the tooltips. 'val' has nothing to do with 'field' in this context, that's just what tooltips display for showing the types of identifiers.

That's what I was trying to say. But if the tooltips <i>always <i>say "val" then there isn't much meaning to the word and we may as well see "aaa: string" rather then "val aaa: string". However, I'd rather have it tell me more, in this case that's it's an object field. > The F# spec here about 'what becomes fields' is subject to change up until VS2010 releases. The spec is also thin in describing this (and lots of other stuff; we have a lot of spec work to do). Thanks for this response -- at least I don't feel so bad now... :) This is where I was coming from -- I didn't mean to say that one couldn't write some boilerplate code and bring up relector everytime you had a question about what was going on, but that it shouldn't be necessary - and I'm sure the docs (and I) will be straightned out. > If you really care about 'what becomes a field', then you may prefer the 'explicit' syntax with val. But 99% of the time I think you ought not care (only if you're very deep into perf, I'm not sure I agree that you have to be "very deep into perf" to "care what becomes a field" -- this seems like a rather germane concept and way "above the radar" on the abstraction scale, especially for a .Net language that's on the CLR and interops with all sorts of other code. Making unneeded fields can effect perf (large objects), and not having needed fields can effect interop (reflection), so there's no perfect default, but the current non-field status of un-used primary c'tor args seems like the right one here - reflection is pretty advanced and using val here seems fine. But if this is "subject to change" past the release, there certainly should be a big warning in the docs that user object sizes could vary greatly with some future release. > in which case you maybe should be using structs instead of classes anyway). This is not the case for many c# programs due to copy overhead of structs over refs, except for gobs of > gen 0 objects, right? -- is there something about f# that makes this choice different? I love f# (have used it at work and at play) and am coming from that place, not trying to complain...

By on 7/28/2009 3:04 PM ()

I'm not sure I agree that you have to be "very deep into perf" to "care what becomes a field" -- this seems like a rather germane concept and way "above the radar" on the abstraction scale, especially for a .Net language that's on the CLR and interops with all sorts of other code. Making unneeded fields can effect perf (large objects), and not having needed fields can effect interop (reflection), so there's no perfect default, but the current non-field status of un-used primary c'tor args seems like the right one here - reflection is pretty advanced and using val here seems fine. But if this is "subject to change" past the release, there certainly should be a big warning in the docs that user object sizes could vary greatly with some future release.

> in which case you maybe should be using structs instead of classes anyway).
This is not the case for many c# programs due to copy overhead of structs over refs, except for gobs of > gen 0 objects, right? -- is there something about f# that makes this choice different?

Yes, sounds like you're right about the perf/reflection/etc trade-offs here (I was trying to summarize too much in a sentence; there is nothing differenc/special about F# when it comes to choosing between structs and classes and whatnot).

The intention/expectation is that by the time VS2010 releases that there will be a fixed spec regarding 'what are the guarantees about what turns into a field' as well is 'if there is any implementation flexibility when it comes to optimizing away fields' (e.g. stuff that could change, either from build to build, or in future versions of the language/compiler).

(My understanding is that the thing that concerns you more is that eventually you want to be ensured that there will be a mechanism to predict/understand/control what become fields, moreso than the particular details of 'what are the rules that say whether this particular let-bound thing or constructor param becomes a field or not' - yes?)

By on 7/28/2009 4:24 PM ()

> The intention/expectation is that by the time VS2010 releases that there will be a fixed spec...

Sounds great...

> ...eventually you want to be ensured that there will be
> a mechanism to predict/understand/control what become fields, moreso than...

Well, as for "predict/understand" - certainly, but sounds like that will be taken care of already by the above. As for "control", I'm happy knowing what's going on, and being able to do things the "old-fashioned way" if I'm not sure. Tools support (tooltips, identifier coloring/format, diagrams, etc.) is also great.

Denotational abstraction is awsome (I'm all for less typing...), as long as predictability doesn't suffer too much - but I suppose one should expect and thrive in a certain amount of TMTOWTDI in f# given it's position.

By on 7/28/2009 5:09 PM ()

even RTFM isn't very explict about these issues.

In this particular circumstance "the manual" is <a href=http://research.microsoft.com/apps/pubs/default.aspx?id=79948>The F# Draft Language Specification</a> in particular section "8.5 Class Types". <code lang=fsharp>type t(a:int,b:int) = member x.SayHello = System.Console.WriteLine("Hello!")</code> t(a:int,b:int) isn't just a constructor - it is the [i]primary constructor[/i]. As such the declaration (a:int,b:int) doesn't just contain constructor parameters but also the fields in the object (additional fields can still be added in the type definition). Current generation functional (hybrid) languages are trying to dispense with the boilerplate code and high ceremony that is so common in languages like Java or C# - type inferencing is the most notable measure but other shortcuts, like primary constructors, are scattered throughout. If you assume immutability by default, it makes sense to assume for the [i]general[/i] and most likely case that the constructor parameter list is identical to the object field layout. <code lang=fsharp>type PairOfIntegers(x:int,y:int) = new (x) = PairOfIntegers(x,x)</code> PairOfIntegers(x:int,y:int) is the primary constructor of a class that defines fields x:int, and y:int in its instances. PairOfIntegers(x) is an [i]additional object constructor[/i] that initializes both x and y fields to the value of the parameter "x". <code lang=fsharp>type PairOfStrings = val s1 : string val s2 : string new (s) = { s1 = s; s2 = s } new (s1,s2) = { s1 = s1; s2 = s2 }</code> PairOfStrings doesn't define a primary constructor - therefore the object fields have to be defined separately. PairOfStrings does define two "additional object constructors". However given the advice in this topic you should stick with: <code lang=fsharp>type PairOfStrings(s1:string,s2:string) = new (s) = PairOfStrings(s,s)</code> When a primary constructor is present, additional constructors have to use the primary or other additional constructors - so ultimately the primary constructor will always be used in the construction of any object instance in the class.

By on 7/28/2009 1:19 PM ()

> Current generation functional (hybrid) languages are trying to dispense with the boilerplate code and high ceremony that is so common in languages like Java or C#

A great thing! - until it becomes less self-documenting (not to mention messier looking, harder to comment/XML-document, etc.) rather than more - if I glance at a class and need to look for primary c'tors, top-level let's (being careful to avoid any deeper ones), etc., then my job becomes harder as code-reader, if not for the code-writer. Luckily, tools are coming to the rescue, and if I can bring up a pretty WPF-rendered view of the class with one keystroke, it's all good.

> If you assume immutability by default, it makes sense to assume for the general and most likely case that the constructor parameter list is identical to the object field layout.

But, aren't records exactly the solution for these "bags of values"?

If you want state+behavior and are opening the door for interop, then it seems like defining your classes longhand seems the most clear.

Hopefully there will also be f# refactorings for these classes of problems. ( "Refactor->Convert vals to primary ctor", backwards again, etc.)

By on 7/28/2009 3:23 PM ()

until it becomes less self-documenting

(not to mention messier looking, harder to comment/XML-document, etc.)

rather than more ... tools are coming to the rescue...

The problems that you are describing are related to the terseness of

many functional languages that goes hand in hand with their

expressiveness. The "longhand" of C# is helpful for beginners and

intermediate programmers but can become "unnecessary noise" for the

more advanced programmer - noise which could bury some important bits.

Also the boiler plate code can become a real drag on productivity as

evidenced by the multitude of features in development tools that

"helpfully" insert the "longhand" for you. Many functional languages

have preferred to let the context imply the boiler plate rather than

having to insert it.

Of course as a consequence code with "boiler plate implied by context"

is harder to read for a novice - probably one of the reasons why some

languages that use the approach experience a relatively slow (and low)

rate of adoption. This is really where the development tools need to

catch up and make the implied information more visible for non-experts

to ease the learning curve - showing the inferred types is only the

first step.

If you want state+behavior and are opening

the door for interop, then it seems like defining your classes longhand

seems the most clear.

Don't forget that F# is still primarily a functional language and isn't

meant to replace C#. So the object oriented constructs exist to a large

extent to enable interop. The syntax, semantics and sensibilities for

defining the OO constructs need to "fit" into the functional world of

F# - which includes "being terse" to reduce "redundant" information as

much as possible, enabling more expressive code (which is also compact;

compact code on the other hand isn't necessarily expressive).

By on 7/28/2009 5:36 PM ()

I made this class here:

1
2
3
4
5
type t(a:int,b:int,c:int) =   
    let bb = b
    member x.SayHello() =
       System.Console.WriteLine("Hello! " ^ a.ToString())
    member x.B = bb

and if we put it through Reflector, we get this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[Serializable, CompilationMapping(SourceConstructFlags.ObjectType)]
public class t
{
    // Fields
    internal int a;
    internal int bb;

    // Methods
    public t(int a, int b, int c)
    {
        Program.t @this = this;
        @this.a = a;
        @this.bb = b;
    }

    public void SayHello()
    {
        Console.WriteLine(Operators.op_Concatenate("Hello! ", this.a.ToString()));
    }

    // Properties
    public int B
    {
        get
        {
            return this.bb;
        }
    }
}

Thus it appears that constructor parameters that are used non-constructor code become fields and other constructor parameters don't.

By on 7/28/2009 1:10 PM ()

Thus it appears that constructor parameters that are used non-constructor code become fields and other constructor parameters don't.

This could just as easily be the result of an "elimination of unused fields" optimization. For the time being I'm going to assume that the [i]intent[/i] behind the primary constructor's parameter list is to:

  1. specify the constructor's object initialization values
  2. specify the user settable fields for all object instances (other "computable" fields can still be added further into the type definition)
By on 7/28/2009 1:31 PM ()

Avoid the form of classes that lack constructor parens right after the type declaration…

Why is that? Is that going away from the language? I may be missing something but that should be the only way to declare a non-public constructor.

By on 7/27/2009 3:50 PM ()

No, you can make the primary constructor private, a la

1
2
3
4
type t private(a,b) =   // note 'private'
    inherit MyClass<string,int>(a,b)
    member x.SayHello =
       System.Console.WriteLine("Hello!")

It's not going away from the language, but it's the preferred form, and 99% of code you'll find will use the preferred form. The syntax for the other form is ugly (especially with regards to fields/constructors/inheritance/initialization).

By on 7/27/2009 3:58 PM ()

…you can make the primary constructor private…

Thanks, I did not know that syntax. Cool!

By on 7/27/2009 4:43 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper