Hi Sebatian,

Yes, I agree this must be fixed. It's high on our priority list.

BTW from 1.9.2.9 you can omit the class/end.

1
2
type Foo(i:int) =
    member m.f n = n+i

Please send detailed comments on the syntax to us (fsbugs AT microsoft DOT com)

Don

By on 9/11/2007 5:08 PM ()

Mostly because of the mutually-recursive issue, I ended up taking a set of implicit-constructor classes and just turned them into record types with type augmentations where necessary. For the simple objects I'm dealing with (wrapping System.Type objects in a sensible hierarchy for a code generator) this is actually nicer.

For example:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
type
    GType = 
    | GPrimitive of PrimitiveType
    | GSpeciallyMarshaled of SpeciallyMarshaledType    
    | GClass of ClassType
    | GInterface of System.Type    
    | GValueType of System.Type
    | GArray of GType * int
    | GUnsupported of System.Type // mostly pointers, which we don't implement right now
    with 
        static member make (t:System.Type) =
            if t.IsPrimitive then 
                if t = (type System.Boolean) then GPrimitive Boolean
                elif t = (type System.Byte) then GPrimitive Byte
                elif t = (type System.Int16) then GPrimitive Int16
                elif t = (type System.Int32) then GPrimitive Int32
                elif t = (type System.Int64) then GPrimitive Int64
                elif t = (type System.IntPtr) then GPrimitive IntPtr
                elif t = (type System.Char) then GPrimitive Char
                elif t = (type System.Double) then GPrimitive Double
                elif t = (type System.Single) then GPrimitive Single
                else failwithf "unsupported primitive type %s." t.Name
                
            // specially marshaled
            elif t = (type System.String) then GSpeciallyMarshaled String 
            elif t = (type System.Void) then GSpeciallyMarshaled Void
            
            elif t.IsArray then GArray (GType.make(t.GetElementType()), t.GetArrayRank()) // check first, b/c arrays are also classes            
            elif t.IsClass then GClass { new ClassType with t=t and members=(t.GetMembers() |> Array.map Member.make) }
            elif t.IsInterface then GInterface t
            elif t.IsValueType then GValueType t

            else GUnsupported t
            
        member t.isSupported = 
            match t with
            | GUnsupported _ -> false
            | _ -> true
        member t.Name =
            match t with
            | GPrimitive (p) -> p.ToString()
            | GSpeciallyMarshaled (s) -> s.ToString()
            | GClass { t=t } -> t.Name
            | GInterface (t) -> t.Name
            | GValueType (t) -> t.Name
            | GArray (t, i) -> sprintf "array rank %i of %s" i t.Name
            | GUnsupported (t) -> sprintf "unsupported type with name (%s)" t.Name
    end
and
    PrimitiveType = Boolean|Byte|Int16|Int32|Int64|IntPtr|Char|Double|Single 
and
    SpeciallyMarshaledType = Void|String
and
    ClassType = { t:System.Type; members: Member array }    
and
    MethodType = { info:System.Reflection.MethodInfo; isStatic:bool; return_typ:GType; parameters:(Parameter array); }
and
    Parameter = { info:System.Reflection.ParameterInfo; name:string; typ:GType }
and
    FieldType = { info:System.Reflection.FieldInfo; name:string; typ:GType }
and
    PropertyType = { info:System.Reflection.PropertyInfo; name:string; typ:GType }
and
    Member = Method of MethodType | Property of PropertyType | Field of FieldType
    with
        static member make(m:System.Reflection.MemberInfo) =
            match m with
            | :? System.Reflection.MethodInfo as m 
                -> Method { new MethodType
                            with info = m
                            and  isStatic = m.IsStatic
                            and  return_typ = GType.make(m.ReturnType)
                            and  parameters = m.GetParameters() 
                                |> Array.map (fun p -> { new Parameter with info=p and name=p.Name and typ=GType.make(p.ParameterType) }) 
                          }
            | :? System.Reflection.FieldInfo as f 
                -> Field { new FieldType with info=f and name=f.Name and typ=GType.make(f.FieldType) }
            | :? System.Reflection.PropertyInfo as p 
                -> Property { new PropertyType with info=p and name=p.Name and typ=GType.make(p.PropertyType) }
            | _ -> 
                failwithf "busted memberinfo! %s" m.Name

It's a bit awkward, as you need to define single concepts twice (e.g. GPrimitive of PrimitiveType). One invariably ends up using the object-expression-like syntax for constructing these things

1
{ new FieldType with info=f and name=f.Name and typ=GType.make(f.FieldType) }

That's a bit verbose, but I actually like it better than a constructor with tupled or curried arguments as those functions are position-dependent. For the simple record-like objects I'm dealing with here, this makes sense.

If I were really designing a more intelligent class hierarchy with smarter constructors, I guess I'd use "real" class types. But then again, they wouldn't have "implicit" constructors, so what would I gain?

This is a phenomenon that seems to be common in F#: two or three slightly different ways (with different syntax) to accomplish similar things. F# is a research language that is constantly being updated with new ideas, so that's somewhat to be expected. But at some point is the desire to support old ideas/syntax making the new concepts more difficult to implement? For instance, why not simply demand all records be constructed with the { new T with p=v and p=v } syntax? And then at that point, since the old { p=v; p=v; } syntax is no longer valid, it can be coopted for more uniformity, perhaps something like {new T with p=v; p=v; } or {p=v; p=v;}:T or T:{p=v;p=v;} (the latter of which is vaguely javascripty and easy on the eyes. OK, now I'm just rambling.

The real question, I suppose, is when should I prefer using {} records with augmentations over classes (eventually with named constructors)?

By on 9/12/2007 4:45 PM ()

Hi Sebastian,

One major aim as we refine the corners of the language is to continue to align records and classes-defined-with-impicit-constructors. For example, "new T(p=v,q=u)" or just "T(p=v,q=u)" should both be acceptable ways of creating record values. Likewise it should be possible to define a class type that supports cloning "{ x with p=v }" syntax.

One significant issue is that union and record types implement structural equality/compare/hash semantics by default for the "="/compare/hash operators, and class types do not (they use object reference equality by default). We plan to make this configurable in both cases: adding an attribute to a record will enable object equality semantics. Likewise we plan to allow you to mark particular fields as "to be ignored" for structural equality. Likewise adding an attribute to class types would enable structural equality semantics, again on a selective basis.

These changes have not yet been scheduled but we consider them important.

Kind regards

don

By on 9/12/2007 5:40 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