I've kind of asked this question earlier so sorry for asking a bit similar question again. But unfortunately im not able to really understand how to design a discriminated unions.
so i have bunch of data structures which look like
type Artist( artistId : int, name : String ) =
do
if name = null then nullArg String.Empty
new(artistId: int) = Artist(artistId)
member x.ArtistId = artistId
member x.Name = name
and Genre() =
let mutable name = String.Empty
let mutable genreId : int = 0
let mutable description = String.Empty
let mutable albums = List.empty
member x.Description
with get() = description and set( value ) = description <- value
member x.Albums
with get() = albums and set ( value ) = albums <- value
and Album() =
let mutable title = String.Empty
let mutable albumId = 0
let mutable genreId = 0
let mutable artistId = 0
let mutable price : decimal = Decimal.Zero
let mutable albumArtUrl = String.Empty
let mutable genre = new Genre()
let mutable artist = new Artist(artistId)
member x.Title
with get() = title and set (value) = title <- value
member x.Genre
with get() = genre and set (value) = genre <- value
member x.AlbumId
with get() = albumId and set ( value ) = albumId <- value
member x.GenreId
with get() = genreId and set ( value ) = genreId <- value
member x.ArtistId
with get() = artistId and set ( value ) = artistId <- value
member x.Price
with get() = price and set ( value ) = price <- value
member x.AlbumArtUrl
with get() = albumArtUrl and set ( value ) = albumArtUrl <- value
member x.Artist
with get() = artist and set ( value ) = artist <- value
enter code here
I tried defining the above as a Discriminated union based on suggestions by some of F# guru's
which i defined like below
type Name = string
type AlbumId = int
type Artist =
| ArtistId of int
| Artist of Name
type Album =
| Title of string
| Price of decimal
| Album of AlbumId * Artist
| AlbumArtUrl of string
type Genre =
| GenreId of int
| Genre of Name * Album list
enter code here
But now i unable to figure out how would i populate my discriminated union similarly i was doing with my simple F# types which are just properties ?.
Can someone help me to explain this ?. I have been reading on discriminated unions but wont say i fully understand them .
Discriminated unions are used to represent types with multiple different cases, which roughly corresponds to class hierarchies in object oriented langauges. For example, a base class Shape with two inherited classes for Circle and Rectangle might be defined like this:
type Shape =
| Rectangle of (float * float) * (float * float) // Carries locations of two corners
| Circle of (float * float) * float // Carries center and diameter
The way you defined your discriminated unions does not really do what you probably intended. Your types Album, Artist and Genre represent just a single concrete type.
You can represent these with either records (which are just like lightweight classes with just properties) or using discriminated unions with a single case, which corresponds to a single class, but has a pretty lightweight syntax, which is the main benefit. For example:
type Name = string
type Price = decimal
type AlbumId = int
type ArtistId = int
type Artist = Artist of ArtistId * Name
type Album = Album of AlbumId * Name * Price * Artist
To construct an artist together with a few albums, you can write:
let pinkFloyd = Artist(1, "Pink Floyd")
let darkSide = Album(1, "The Dark Side of the Moon", 12.0M, pinkFloyd)
let finalCut = Album(2, "The Final Cut", 11.0M, pinkFloyd)
If you then create a genre, that will contain a list of albums and possibly a list of artists, so you could write something like this:
type Genre = Genre of Name * Artist list * Album list
let rock = Genre("Rock", [pinkFloyd], [darkSide; finalCut])
The question now is, how do you actually want to populate the types. What is your data-source? If you're loading data from a database or from a XML file, you're probably want to write a function that takes some part of the data source and returns Artist or Album and after you load all albums and artists, wrap them inside a Genre and return that as a final result.
PS: It is a bit difficult to answer your questions, because you're not really giving a bigger picture of what you're trying to do. If you can give a small, but concrete example (including the loading of data and their use), then someone can help you to look at the problem from a more functional perspective.
Related
I created a custom type which is implementing a Map.
type School = Map<int, string list>
I tried now various ways on how to instatiate that type but it always fails.
With attempt Nr.1 I thought maybe one can 'dot' the class (somehow).
let xyz = School.Map.empty;;
or
let kgse = School.empty;;
//The type 'Map<Key,Value>' does not define the field, constructor or member 'empty'.
Attempt Nr.2 was my hope that f# knows if I create a map which has the same structure of the custom type it assigns it automatically.
let xyz =
- Map.empty.
- Add(2, ["Alex"]);;
val xyz: Map<int,string list> = map [(2, ["Alex"])]
This works but it only returns the general Map class.
Finally, I thought maybe I can cast the type.
let xyz =
- School Map.empty.
- Add(2, ["Alex"]);;
This throw me again an error:
Successive arguments should be separated by spaces or tupled, and arguments involving function or method applications should be parenthesized.
F# have pretty neat feature - types with same name can extend each other. For example System.Collection.Generic have type EqualityComparer with static property Default, which return adequate comparer for given generic type, but it doesn't fit for collections, because they would be compared be reference, instead of by value.
In C# you can't write extensions for static class to call EqualityComparer<T>.ForCollection, but you can with F#:
module EqualityComparer =
let ForCollection<'a> = ...
let def = EqaulityComparer.Default
let mine = EqualityComparer.ForCollection
As you can see, we extended static class with module. This is 2 distinct types with same name and we can use methods and properties from both.
Same happens with Map class and Map module. You've created alias for type which can be instantiated but haven't for helper module. What you need to do is create type abbreviation for module
type School = Map<int, string list>
module School = Map
School.empty
Part of the problem here is that School, as you've defined it, is a type abbreviation, not a distinct type of its own. This means that it is simply another name for Map<int, string list>. That's a good light-weight approach, and still allows you to create your own School.empty value, if you want:
module School =
let empty : School = Map.empty
let xyz = School.empty.Add(2, ["Alex"])
If, on the other hand, you actually want School to be a real type, you should consider defining it as a record or discriminated union instead:
type School =
private MkSchool of Map<int, string list> with
member this.Add(key, values) =
let (MkSchool map) = this
MkSchool (map.Add(key, values))
module School =
let empty = MkSchool Map.empty
let xyz = School.empty.Add(2, ["Alex"])
Add a School type annotation to xyz:
let xyz : School = Map.empty.Add(2, ["Alex"])
Using dotnet fsi:
> type School = Map<int, string list>
-
- let xyz : School = Map.empty.Add(2, ["Alex"]);;
type School = Map<int,string list>
val xyz : School = map [(2, ["Alex"])]
You can also create functions to return School and then use them as follows:
Again, in dotnet fsi:
> let makeSchool s : School = Map.empty.Add s
-
- let addStudent (school: School) student : School = school.Add student
-
- let xyz' = makeSchool (3, ["Betty"])
-
- let newStudent = (4, ["Charles"])
-
- let schoolWithNewStudentAdded = addStudent xyz' newStudent
- ;;
val makeSchool : int * string list -> School
val addStudent : school:School -> int * string list -> School
val xyz' : School = map [(3, ["Betty"])]
val newStudent : int * string list = (4, ["Charles"])
val schoolWithNewStudentAdded : School =
map [(3, ["Betty"]); (4, ["Charles"])]
I have two kinds of entity in my application: customers and products. They are each identified at a database level by a UUID.
In my F# code, this can be represented by System.Guid.
For readability, I added some types like this:
open System
type CustomerId = Guid
type ProductId = Guid
However, this does not prevent me from using a ProductId as a CustomerId and vice-versa.
I came up with a wrapper idea to prevent this:
open System
[<Struct>]
type ProductId =
{
Product : Guid
}
[<Struct>]
type CustomerId =
{
Customer : Guid
}
This makes initialization a little more verbose, and perhaps less intuitive:
let productId = { Product = Guid.NewGuid () }
But it adds type-safety:
// let customerId : CustomerId = productId // Type error
I was wondering what other approaches there are.
You can use single-case union types:
open System
[<Struct>]
type ProductId = ProductId of Guid
[<Struct>]
type CustomerId = CustomerId of Guid
let productId = ProductId (Guid.NewGuid())
Normally we add some convenient helper methods/properties directly to the types:
[<Struct>]
type ProductId = private ProductId of Guid with
static member Create () = ProductId (Guid.NewGuid())
member this.Value = let (ProductId i) = this in i
[<Struct>]
type CustomerId = private CustomerId of Guid with
static member Create () = CustomerId (Guid.NewGuid())
member this.Value = let (CustomerId i) = this in i
let productId = ProductId.Create ()
productId.Value |> printfn "%A"
Another approach, which is less common, but worth mentioning is to use so-called phantom types. The idea is that you will have a generic wrapper ID<'T> and then use different types for 'T to represent different types of IDs. Those types are never actually instantiated, which is why they're called phantom types.
[<Struct>]
type ID<'T> = ID of System.Guid
type CustomerID = interface end
type ProductID = interface end
Now you can create ID<CustomerID> and ID<ProductID> values to represent two kinds of IDs:
let newCustomerID () : ID<CustomerID> = ID(System.Guid.NewGuid())
let newProductID () : ID<ProductID> = ID(System.Guid.NewGuid())
The nice thing about this is that you can write functions that work with any ID easily:
let printID (ID g) = printfn "%s" (g.ToString())
For example, I can now create one customer ID, one product ID and print both, but I cannot do equality test on those IDs, because they're types do not match:
let ci = newCustomerID ()
let pi = newProductID ()
printID ci
printID pi
ci = pi // Type mismatch. Expecting a 'ID<CustomerID>' but given a 'ID<ProductID>'
This is a neat trick, but it is a bit more complicated than just using new type for each ID. In particular, you will likely need more type annotations in various places to make this work and the type errors might be less clear, especially when there is generic code involved. However, it's worth mentioning this as an alternative.
Im building a sample application where my types hierarchy isnt working with types ordering in Visual Studio. No matter what way i try to arrange the files ( up , down ) I cannot get all the classes be defined.
So in the order they are in f# project
type Artist() =
let mutable artistId = 0
let mutable name = String.Empty
member x.ArtistId
with get() = artistId
and set (value) = artistId <- value
member x.Name
with get() = name
and set ( value ) = name <- value
type Genre() =
let mutable name = String.Empty
let mutable genreId = 0
let mutable description = String.Empty
let mutable albums = [new Album()]
member x.Name
with get() = name
and set (value) = name <- value
member x.GenreId
with get() = genreId
and set ( value ) = genreId <- value
member x.Description
with get() = description
and set ( value ) = description <- value
member x.Albums
with get() = albums
and set ( value ) = albums <- value
and Album() =
let mutable title = String.Empty
let mutable genre = new Genre()
let mutable albumId = 0
let mutable genreId = 0
let mutable artistId = 0
let mutable price : decimal = Decimal.Zero
let mutable albumArtUrl = String.Empty
let mutable artist = new Artist()
member x.Title
with get() = title
and set (value) = title <- value
member x.Genre
with get() = genre
and set (value) = genre <- value
member x.AlbumId
with get() = albumId
and set ( value ) = albumId <- value
member x.GenreId
with get() = genreId
and set ( value ) = genreId <- value
member x.ArtistId
with get() = artistId
and set ( value ) = artistId <- value
member x.Price
with get() = price
and set ( value ) = price <- value
member x.AlbumArtUrl
with get() = albumArtUrl
and set ( value ) = albumArtUrl <- value
member x.Artist
with get() = artist
and set ( value ) = artist <- value
So in above case i get the error "Album" is not defined.
Is there a way to solve this ?. Or i just have to rethink the whole of the hierarchy structure for my types?
If you need to define two types that are mutually recursive (meaning that they can both refer to each other), then you need to place them in a single file and use type ... and ... syntax.
In your example, this means that Genre and Album need to be defined like this:
// Start a definition block using 'type' as normal
type Genre() =
let mutable name = String.Empty
let mutable albums = [new Album()]
member x.Name
with get() = name
and set (value) = name <- value
member x.Albums
with get() = albums
and set ( value ) = albums <- value
// Continue single type definition block using 'and'
and Album() =
let mutable genre = new Genre()
let mutable albumId = 0
let mutable artist = new Artist()
member x.Genre
with get() = genre
and set (value) = genre <- value
member x.AlbumId
with get() = albumId
and set ( value ) = albumId <- value
member x.Artist
with get() = artist
and set ( value ) = artist <- value
However, your example is using F# in a very C#-style, so the code does not really look very elegant and it may not give you many of the benefits of functional programming.
If I wanted to represent a structure that you're using, then I probably wouldn't add reference to the genre into the Album type. When you place a list of albums inside a Genre, you will always be able to recover the genre when you process the data structure (i.e. to turn it into some other structure, maybe an F# record, that can be passed to data-binding). The beneift of F# is that it lets you write the domain on a few lines, but that works only for functional types.
Using discriminated unions with a single case, you can write:
// Type aliases to make code more readable
type Name = string
type AlbumID = int
// Simple type definitions to represent the domain
type Artist = Artist of Name
type Album = Album of AlbumID * Artist
type Genre = Genre of Name * Album list
Suppose I have this class:
type Pet (name:string) as this =
let mutable age = 5
let mutable animal = "dog"
I want to be able to create a new Pet based on some serialized data, which I represent with this record:
type PetData = {
name : string
age : int
animal : string
}
(TLDR: I can't figure out the syntax to make a constructor that'll take a PetData to populate the let bindings. My various attempts follow.)
So I make a new Pet constructor that'll assign values to the let bindings. I try using the class initializer syntax:
new (data:PetData) =
Pet(name,
age = data.age,
animal = data.animal
)
Hmm, nope: No accessible member or object constructor named 'Pet' takes 1 arguments. The named argument 'age' doesn't correspond to any argument or settable return property for any overload.
I check to make sure I've got all the syntax: no missing commas, correct "assignment" (cough) operator, correct indentation.
Okay the, I'll try the record initializer syntax.
new (data:PetData) =
{
name = data.name;
age = data.age;
animal = data.name
}
Error: The type 'Pet' does not contain a field 'name'
Okay, so I need to call the main constructor. I guess there are probably two places I can put it, so let's try both:
new (data:PetData) =
{
Pet(data.name);
age = data.age;
animal = data.name
}
Nope: Invalid object, sequence or record expression
new (data:PetData) =
Pet(data.name)
{
age = data.age;
animal = data.name
}
And nope: This is not a valid object construction expression. Explicit object constructors must either call an alternate constructor or initialize all fields of the object and specify a call to a super class constructor.
I didn't want to have to do this, but maybe since the fields are mutable anyway, I can just assign values to the object after initializing it:
new (data:PetData) =
let p = Pet(data.name)
p.age <- data.age
p.animal <- data.animal
p
Type constraint mismatch. The type Pet is not compatible with type PetData The type 'Pet' is not compatible with the type 'PetData'
Lol, what??
Okay, let's try this:
let assign(data:PetData) =
this.age <- data.age
this.animal <- data.animal
new (data:PetData) =
let p = Pet(data.name)
p.assign(data)
p
The field, constructor or member 'assign' is not defined
Right, so it can't access let bindings from outside.
Let's try a member then:
new (data:PetData) =
let p = Pet(data.name)
p.Assign(data)
p
member x.Assign(data:PetData) =
this.age <- data.age
this.animal <- data.animal
This is not a valid object construction expression. Explicit object constructors must either call an alternate constructor or initialize all fields of the object and specify a call to a super class constructor.
Okay... let's try this whole thing differently then, using explicit fields:
type Pet =
[<DefaultValue>]val mutable private age : int
[<DefaultValue>]val mutable private animal : string
val private name : string
new(name:string) =
{ name = name }
new(data:PetData) =
{
name = data.name;
age = data.age;
animal = data.animal
}
Extraneous fields have been given values
And that's when I punch my elderly cat in the face.
Any other ideas? These error messages are throwing me off. I can't even find half of them on Google.
You could do this.
type Pet =
val mutable private age : int
val mutable private animal : string
val private name : string
new (name:string) =
{
name = name;
age = 5; // or age = Unchecked.defaultof<_>;
animal = "dog"; // or animal = Unchecked.defaultof<_>;
}
new (data:PetData) =
{
name = data.name;
age = data.age;
animal = data.animal;
}
F# has its own style which looks like this.
type Pet(name:string, age:int, animal:string) =
let mutable age = age
let mutable animal = animal
new (name:string) =
Pet(name, 5, "dog")
new (data:PetData) =
Pet(data.name, data.age, data.animal)
Edit
Added an event used in do per comment request.
type Pet(name:string, age:int, animal:string, start:IEvent<string>) =
let mutable age = age
let mutable animal = animal
// all three constructors will call this code.
do start.Add (fun _ -> printf "Pet was started")
new (name:string, start:IEvent<_>) =
// an example of different logic per constructor
// this is called before the `do` code.
let e = start |> Event.map (fun x -> x + " from 'name constructor'")
Pet(name, 5, "dog", e)
new (data:PetData, start:IEvent<_>) =
Pet(data.name, data.age, data.animal, start)
Let bindings in a type are private and there's not much you could do about that. As such you cannot use Named Arguments. By creating properties you can do it like so, but not from inside the Pet type:
type Pet (name:string) =
let mutable age = 5
let mutable animal = "dog"
member x.Age with get () = age and set v = age <- v
member x.Animal with get () = animal and set v = animal <- v
type PetData = {
name : string
age : int
animal : string
}
with
member x.ToPet =
new Pet (x.name, Age = x.age, Animal = x.animal)
The other option would be to create a more general constructor like Gradbot suggested, either accepting a PetData object directly or all three parameters.
I'm writing a generic class that has two constructors: the first one initializes every field, the second (parameter-less) should not initialize anything.
The only way I found to achieve this is calling the main constructor with "empty" arguments, i.e. Guid.Empty and null. Besides not looking good functional style to my untrained eyes, this means that I have to put a a' : null constraint on the second parameter, which I don't want:
type Container<'a when 'a : null>(id : Guid, content : 'a) =
let mutable _id = id
let mutable _content = content
new() = Container<'a>(Guid.Empty, null)
member this.Id
with get() = _id
and set(value) = _id <- value
member this.Content
with get() = _content
and set(value) = _content <- value
I see two ways to solve this:
use something like the default c# keyword instead of null (does such a thing exist in F#?)
use a different syntax to specify constructors and private fields (how?)
What is the best way to implement this class?
The F# analog to default is Unchecked.default<_>. It is also possible to use explicit fields which you don't initialize:
type Container<'a>() =
[<DefaultValue>]
val mutable _id : Guid
[<DefaultValue>]
val mutable _content : 'a
new (id, content) as this =
new Container<'a>() then
this._id <- id
this._content <- content
However, in general, your overall approach is somewhat unidiomatic for F#. Typically you'd use a simple record type (perhaps with a static method to create uninitialized containers, although this seems to have questionable benefit):
type 'a Container = { mutable id : Guid; mutable content : 'a } with
static member CreateEmpty() = { id = Guid.Empty; content = Unchecked.defaultof<_> }
In many situations, you could even use an immutable record type, and then use record update statements to generate new records with updated values:
type 'a Container = { id : Guid; content : 'a }
[<GeneralizableValue>]
let emptyContainer<'a> : 'a Container =
{ id = Guid.Empty;
content = Unchecked.defaultof<_> }
let someOtherContainer = { emptyContainer with content = 12 }
If the type will be used from languages other than F#, the following provides a natural interface in F#, and C#, for example.
type Container<'a>(?id : Guid, ?content : 'a) =
let orDefault value = defaultArg value Unchecked.defaultof<_>
let mutable _id = id |> orDefault
let mutable _content = content |> orDefault
new() = Container(?id = None, ?content = None)
new(id : Guid, content : 'a) = Container<_>(?id = Some id, ?content = Some content)
member this.Id
with get() = _id
and set(value) = _id <- value
member this.Content
with get() = _content
and set(value) = _content <- value
If it will only be used from F#, you can omit the following constructor overloads
new(id : Guid, content : 'a) = Container<_>(?id = Some id, ?content = Some content)
new() = Container()
because the overload accepting optional args handles both these cases equally well in F#.