F# - constructing nested types

眉间皱痕 提交于 2021-02-19 07:12:56

问题


I guess this is pretty basic F# question:

Types are:

type Id1 =
    | Id1 of int

type Id2 =
    | Id2 of string

type Id =
    | Id1
    | Id2

type Child = {
    Id : Id;
    Smth : string list
    }

type Node =
    | Child of Child
    | Compos of Node * Node

where Node and Child should represent replacement for Composite OOP design pattern.

The problem is that I cannot instantiate types in this way:

let i1 : Child = {Id = Id1(1); Smth = []}   //Id1 value is not a function and cannot be applied
let i2 : Child = {Id = Id1(1); Smth = []}   //same
let i3 : Node = Compos(i1, i2)  //Expression expected Node but has Child

where compiler errors are given in the comments.


回答1:


Your definition of Id has the same semantics as the following:

type T = int | string

Such type, which is just a mix of two other types, is commonly referred to as "non-discriminated union". To be fair, there are languages that support non-discriminated unions (e.g. TypeScript), but F# is not one of them. F# is part of the ML family of languages, which support discriminated unions. The "discriminated" part there means that the parts of the union need to be given a distinguishing property, a "discriminator", which is more commonly called "constructor". For example:

type T = X of int | Y of string

Here, X and Y are constructors of type T. They are used when constructing values to tell which variant is being constructed:

let t = X 42

And they are used during pattern matching to tell which variant is expected:

let f t = match t with
    | X n -> printfn "it's a number: %d" n
    | Y s -> printfn "it's a string: %s" s

You actually have one of those in your own code - Node, - so it baffles me a bit why you made this mistake with Id, but not with Node.

If you want to construct values of Id the way you do in the definition of i1 and i2 - i.e. Id1(1), - then you need to make Id a discriminated union with one of the constructors named Id1 and taking an int as parameter. From the rest of your code I can guess that the other constructor should be Id2 and take a string:

type Id = Id1 of int | Id2 of string

Now, an interesting thing to note is that, while my example type T above wouldn't compile, your type Id compiles just fine. Why is that?

The reason is that int and string have the wrong capitalization (union case identifiers must be uppercase), but Id1 and Id2 are fine in that respect. When you define your type as type Id = Id1 | Id2, that is perfectly legal, but those Id1 and Id2 have no relation to the previously defined types Id1 and Id2. They just happen to have the same name, and having the same name as previous definitions is allowed in F#. It is called "shadowing".

Your type Id ended up having two constructors, Id1 and Id2, both of which have no parameters. This is why the compiler complains when you're trying to pass 1 as parameter to Id1 as you write Id1(1): "Id1 is not a function and cannot be applied"



来源:https://stackoverflow.com/questions/58070676/f-constructing-nested-types

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!