F# quotations, arrays and self-identifier in constructors
Asked Answered
C

1

5

I think that's a well-known limitation of F# but I couldn't find any good workarounds…

So, here is the code (I tried to make it as simple as possible, so probably it looks like it doesn't make any sense):

[<ReflectedDefinition>]
type Human (makeAName: unit -> string) as self =
    let mutable cats : Cat array = [| |]
    do
        // get a cat
        cats <- Array.append cats [| new Cat (self, makeAName ()) |]
    member this.Cats = cats
and
 [<ReflectedDefinition>]
 Cat (owner : Human, name : string) = class end

The compiler says:

error FS0452: Quotations cannot contain inline assembly code or pattern matching on arrays

Actually it is the combination of as self and array property getter that breaks everything.

The points here are:

  • I really want to use arrays, because I want WebSharper to translate my collections to JavaSript arrays.
  • I really need a self-identifier in constructors.
  • I really need classes (i.e. functional style won't work).
  • Per-method self-identifiers (member this.Foo) work fine.

One workaround I can think of is making constructors private and using static methods to construct objects. This way I don't need as self. But it is just silly.

Are there any better options?


Update:

Here is an even simpler example:

[<ReflectedDefinition>]
type User (uid: int) as self =
    let ROOT_UID = 0
    member this.isRoot = (uid = ROOT_UID)

With as self I can't even define a class constant. Well, it's actually a separate question, but I'll ask it here: how do I define a class constant in this particular case?


Collayer answered 28/3, 2013 at 8:29 Comment(0)
M
8

I do not think it is silly at all. We actually prefer static constructor methods for clarity, even in code that does not use WebSharper. In the whole IntelliFactory codebase we rarely, if ever use self.

You are hitting two annoying limitations of F# compiler and quotations. As you point out, static methods can solve the self problem:

[<ReflectedDefinition>]
type Human private (cats: ref<Cat []>) =
    member this.Cats = !cats

    static member Create(makeAName: unit -> string) =
        let cats = ref [| |]
        let h = Human(cats)
        let cat = Cat(h, makeAName())
        cats := [| cat |]
        h

and [<ReflectedDefinition>] Cat (owner: Human, name: string) =
    class
    end

There are many other ways to accomplish this, for example you can get rid of ref indirection.

Second, you often get FS0452 in ReflectedDefinition code with array operations, even in plain static methods. This usually can be resolved by using library functions instead of direct array access (Array.iter, Array.map).

For the second example, you really want this:

[<ReflectedDefinition>]
module Users =

    [<Literal>]     
    let ROOT_UID = 0

    type User(uid: int) =
        member this.isRoot = (uid = ROOT_UID)

The [<Literal>] annotation will let you pattern-match on your constants, which can be handy if there is more than one.

For your points:

  1. I really want to use arrays - that should be OK
  2. I really need a self-identifier - it is never necessary, just as constructors are not
  3. I really need classes (i.e. functional style won't work) - definitely not true
  4. Per-method self-identifiers (member this.Foo) work fine - yes, and are useful
Micamicaela answered 28/3, 2013 at 11:58 Comment(1)
Static constructors—fine.Speaking about the second example, well, right, module constant does work but I that's actually a class constant that's why I want to put in in the class. Having class constants inside classes (and not polluting module namespace) seems like a reasonable thing to do.Collayer

© 2022 - 2024 — McMap. All rights reserved.