Hi,

it is possible to do this with a WebSharper macro.

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
open WebSharper
open WebSharper.JavaScript

module Macros =
    open WebSharper.Core.Macros
    module Q = WebSharper.Core.Quotations
    module C = WebSharper.Core.JavaScript.Core

    type NameOfMacro() =
        interface IMacroDefinition with
            member this.Macro =
                {
                    Body = None  
                    Requirements = []
                    Expand = fun tr q ->
                        match q with
                        | Q.CallModule (c, []) ->
                            match c.Generics with
                            | [t] -> C.Constant (C.String t.FullName) 
                            | _ -> failwith "NameOfMacro error"
                        | _ -> failwith "NameOfMacro error"
                }

[<JavaScript>]
module Client =

    [<Macro(typeof<Macros.NameOfMacro>)>]
    let nameof<'a> = X<string>

We will look into exposing some helpers for macro creation, and documenting it.

By on 3/6/2015 6:51 AM ()

Thanks for the reply. All I need is something that will work at compile time so I tried your first solution. I may be misunderstanding how that code is supposed to be applied because it throws an exception during the build process and since it doesn't even print a stack trace, I have no idea what the problem is.

Here's some example code that reproduces the issue:

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
84
85
86
namespace TestTypeOf

open WebSharper

module Macros =
    open WebSharper.Core.Macros
    module Q = WebSharper.Core.Quotations
    module C = WebSharper.Core.JavaScript.Core

    type TypeNameMacro() =
        interface IMacroDefinition with
            member this.Macro =
                {
                    Body = None  
                    Requirements = []
                    Expand = fun tr q ->
                        match q with
                        | Q.CallModule (c, []) ->
                            match c.Generics with
                            | [t] -> C.Constant (C.String t.FullName) 
                            | _ -> failwith "TypeNameMacro error"
                        | _ -> failwith "TypeNameMacro error"
                }

[<AutoOpen>]
module TypeName =
    [<Macro(typeof<Macros.TypeNameMacro>)>]
    let typeNameOf<'a> = typeof<'a>.FullName

[<JavaScript; AutoOpen>]
module UsesMacro =
    type Callback<'a> = 'a -> unit
    type TypedCallbacks<'a> = Map<int, Callback<'a>>
    type Callbacks = Map<string, obj>

    let GetTypedCallbacks (callbacks:Callbacks) =
        match callbacks.TryFind typeNameOf<'a> with
        | None -> Map.empty
        | Some callbacks -> callbacks :?> TypedCallbacks<'a>

    let SetTypedCallbacks (typedCallbacks:TypedCallbacks<'a>) (callbacks:Callbacks) : Callbacks =
        callbacks.Add(typeNameOf<'a>, typedCallbacks)

[<AutoOpen>]
module Controls =
    open WebSharper.Html.Client
    open WebSharper.JavaScript

    type TestControl() =
        inherit Web.Control()

        [<JavaScript>]
        override this.Body =
            let callbacks: Callbacks = Map.empty
            let typedCallbacks = GetTypedCallbacks callbacks
            let typedCallbacks =
                typedCallbacks.Add(1,
                                   (fun (arg: string) -> Console.Log "asdf"))
            let callbacks = SetTypedCallbacks typedCallbacks callbacks

            for pair in GetTypedCallbacks callbacks do
                let callback = pair.Value
                callback()

            Div [] :> _

open WebSharper.Sitelets

module Site =
    open WebSharper.Html.Server

    type Action = | Index

    let IndexContent : Content<Action> =
        PageContent <| fun context ->
            {Page.Default with
                Title = Some "Test"
                Body = [Div [new TestControl()]]}

    type Site() =
        interface IWebsite<Action> with
            member this.Sitelet = Sitelet.Content "/" Index IndexContent
            member this.Actions = [Index]

[<assembly: Website(typeof<Site.Site>)>]
do ()

The error message is Exception of type 'WebSharper.Core.Reflection+InvalidTypeException' was thrown. Double-clicking on the error in the build status window takes me to the WebSharper.targets file, line 54 - the beginning of the WebSharperTask tag in the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
<Target Name="WebSharperCompile" AfterTargets="CoreCompile">
  <WebSharperTask Command="Compile" ItemInput="@(IntermediateAssembly);@(ReferencePath)"
    Configuration="$(Configuration)"
    EmbeddedResources="@(EmbeddedResource)"
    KeyOriginatorFile="$(KeyOriginatorFile)"
    MSBuildProjectDirectory="$(MSBuildProjectDirectory)"
    Name="$(AssemblyName)"
    WebProjectOutputDir="$(WebProjectOutputDir)"
    WebSharperBundleOutputDir="$(WebSharperBundleOutputDir)"
    WebSharperSourceMap="$(WebSharperSourceMap)"          
    WebSharperHtmlDirectory="$(WebSharperHtmlDirectory)"
    WebSharperProject="$(WebSharperProject)" />
</Target>
By on 3/6/2015 6:40 PM ()

Hi, sorry, there is no way to have a type function passed generic argument look up type information in current WebSharper translation, because its (JavaScript) runtime values has no type information attached.

This typeNameOf function will work only with a specific type passed to it, so typeNameOf<string> will be replaced to 'System.String' in the generated code by the macro, but you can't use it as a type function on a type parameter as it works in .NET.

By on 3/27/2015 2:06 AM ()

Sorry, I wasn't answering your question fully. This only works if the type parameter of nameof is known at compile time. Type information of runtime values are not tracked by WebSharper, but we can define a macro to wrap a value with its .NET type name:

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
open WebSharper
open WebSharper.JavaScript

module Macros =
    open WebSharper.Core.Macros
    module Q = WebSharper.Core.Quotations
    module C = WebSharper.Core.JavaScript.Core

    type TypedMacro() =
        interface IMacroDefinition with
            member this.Macro =
                {
                    Body = None  
                    Requirements = []
                    Expand = fun tr q ->
                        match q with
                        | Q.CallModule (c, [x]) ->
                            match c.Generics with
                            | [t] -> 
                                try
                                    C.NewObject [
                                        "Value", tr x
                                        "TypeName", C.Constant (C.String t.FullName) 
                                    ]
                                with _ -> failwith "typed<_> cannot be used on a generic typed value"
                            | _ -> failwith "NameOfMacro error"
                        | _ -> failwith "NameOfMacro error"
                }

type Typed<'a> =
    {
        Value: 'a
        TypeName : string
    } 

[<Macro(typeof<Macros.TypedMacro>)>]
let typed x =
    {
        Value = x
        TypeName = x.GetType().FullName 
    }

So typed<_> creates a record from a value where you need to persist the type name, but the type of that value still have to be known at compile time. Maybe you could write a computation expression builder which simplifies dealing with these. But generic operations and functions are still a problem...

By on 3/6/2015 7:15 AM ()
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