If I've got a record type that contains all the database functions like so
type Database =
{ getThingsByCountThenLength: int -> int -> Thing list
getUsersByFirstNameThenLastName: string -> string -> User list }
Is there any way to name the input parameters, so it's more clear? Something like the following (which doesn't compile)
type Database =
{ getThings: count:int -> length:int -> Thing list
getUsers: firstName:string -> lastName:string -> User list }
(Note it does work for interfaces; I just want it for records.)
type IDatabase =
abstract getThings: count:int -> length:int -> Thing list
abstract getUsers: firstName:string -> lastName:string -> User list
This is not a direct answer (I don't think there is one), but as an alternative you can use single-case union types, which will not only add clarity and continue to allow currying, but also enforce compile-time correctness.
type Count = Count of int
type Length = Length of int
type FirstName = FirstName of string
type LastName = LastName of string
type Database =
{ getThings: Count -> Length -> Thing list
getUsers: FirstName -> LastName -> User list }
Type aliases might be what you want:
type count = int
type length = int
type firstName = string
type lastName = string
type Database =
{ getThings: count -> length -> Thing list
getUsers: firstName -> lastName -> User list }
Though, in this case, they look rather weird
Other option is using a record instead
type whatever = {
count : int;
length : int;
}
let param = { count = 1; length = 1; }
param |> printfn "%A"
As already mentioned, I don't think you can name parameters of a function inside a record.
However, you can name parameters of members inside an interface. If you were happy with using an interface, then you could write something like this:
type IDatabase =
abstract GetThingsByCountThenLength :
count:int -> length:int -> Thing list
abstract GetUsersByFirstNameThenLastName :
firstName:string -> lastName:string -> User list
For types that are used across multiple files, I generally prefer to follow the .NET coding style for this and so I would probably choose an interface - unless there are some good reasons for choosing a record. However, this is certainly a matter of personal preference & style.
Related
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.
I have a variable declared like this:
type Variable = {
name : string
id : int
datatype : Object
}
Later on, I would like to do something like this:
match variable.datatype with
| :? System.Byte -> printf "Byte"
| :? System.Double -> printf "Double"
| _ -> printf -> "Other type"
My initial attempt was to declare variable like so (A):
let variable = { name = "foo"; id = 0; datatype = System.Byte }
However, this results in datatype containing something like <fun:variable#31>, and the match doesn't behave as desired - it always hits the "other" case.
I found a workaround, namely
let variable = { name = "foo"; id = 0; datatype = Unchecked.defaultof<Byte> }. However, this doesn't express the intention as clearly.
How can I improve the declaration of Variable to have datatype contain a Type, so that the declaration in (A) works?
I'm trying to learn F# and .NET, there is no production code or homework involved here. So there aren't any constraints apart from having the desired match behavior on basic value types and possibly string as well. It is obvious that I'm missing some basic knowledge (vocabulary relating to language features, etc) that could solve this problem quite easily, but I've hit a roadblock trying to figure out what that might be.
One solution is to create a simple wrapper type for datatype:
type DataType =
| Byte
| Char
| Int
| Double
| String
(Aside: It looks odd to reuse language keywords in this way.)
The declaration for Variable then becomes:
type Variable = {
name : string
id : int
datatype : DataType
}
Declare variable in the way we wanted: let variable = { name = "foo"; id = 0; datatype = Byte }.
Then writing the match statement in this way does the job:
match variable.datatype with
| Byte -> printf "Byte"
| Double -> printf "Double"
| _ -> printf "Other type"
I think I have one half of an answer. As Fyodor Soikin mentioned, we can use System.Type instead of System.Object.
type Variable = {
name : string
id : int
datatype : System.Type
}
let variable = { name = "foo"; id = 0; datatype = typeof<System.Byte> }
let (|IsType|_|) (vartype: System.Type) (variable: Variable) =
if variable.datatype = vartype then Some () else None
let ByteType = typeof<System.Byte> // why do I need these let-bindings
let DoubleType = typeof<System.Double> // ahead of the match construct ?
match variable with
| IsType ByteType -> printf "Byte"
| IsType DoubleType -> printf "Double"
| _ -> printf "Other type"
Edit: I am still not sure how to write the pattern matching. In particular, I can't figure out why I am not allowed to put typeof<System.Byte> directly in the match part of the code.
My intent is to define a module with functions which can operate on all records types which comply with certain assumptions about the keys.
To illustrate, let us have the following code:
> type DBRow = { id: string ; createdAt: System.DateTime } ;;
type DBRow =
{id: string;
createdAt: System.DateTime;}
> let logCreationInfo row = printf "Record %s created at %s " row.id (row.createdAt.ToString()) ;;
val logCreationInfo : row:DBRow -> unit
I would like to change the above logCreationInfo to be able to operate on all records which have id: string and createdAt: System.DateTime (and maybe other things).
Coming from typescript's structural typing, I'd have expected this to be trivial, but I am exploring the possibility that there is a more idiomatic way to handle this in F#.
I had attempted to handle this using interfaces, but even if that could work, since F# supports only explicit interfaces, this will not be suitable for types I don't define myself.
You could use statically resolved type constraints.
let inline logCreationInfo (x : ^t) =
printfn "Record %s created at %s"
(^t : (member id : string) (x))
((^t : (member createdAt : System.DateTime) (x)).ToString())
F# largely uses nominative typing - this is a natural choice in its runtime environment, as this is what Common Type System specification prescribes. Adherence to that set of rules allows F# code to near-seamlessly interoperate with other .NET languages.
It's worth noting that this follows the same reasoning as to why TypeScript uses structural typing. Since that language builds up on top of dynamically typed JavaScript, it's more natural to express object relationships in terms of their structure rather than nominal types - which are a foreign concept in JS.
F# does have a "backdoor" for structural typing through already mentioned SRTPs, but I would suggest using it very sparingly. SRTPs are resolved and the code using them is inlined by the compiler, making for longer compilation times and reduced interoperability with other languages and the .NET platform in general (simply put, you can't refer to that code from other languages or using reflection API, because it's "compiled away").
Usually there are other solutions available. Interfaces were already mentioned, though the example used was a bit contrived - this is simpler:
type IDBRow =
abstract Id: string
abstract CreatedAt: System.DateTime
type Person =
{
id: string
name: string
age: int
createdAt: System.DateTime
}
interface IDBRow with
member this.Id = this.id
member this.CreatedAt = this.createdAt
let logCreationInfo (row: #IDBRow) =
printf "Record %s created at %s" row.Id (string row.CreatedAt)
let x = { id = "1"; name = "Bob"; age = 32; createdAt = DateTime.Now }
logCreationInfo x
Or using composition and a generic type to capture the generic part of what it means to be a DBRow:
type DBRow<'data> =
{
id: string
data: 'data
createdAt: System.DateTime
}
type Person =
{
name: string
age: int
}
let logCreationInfo (row: DBRow<_>) =
printf "Record %s created at %s" row.id (string row.createdAt)
let x = { id = "1"; data = { name = "Bob"; age = 32 }; createdAt = DateTime.Now }
logCreationInfo x
Here's a version with interfaces:
open System
type DBRow1 = {
id: string
createdAt: DateTime
}
type DBRow2 = {
id: string
createdAt: DateTime
address: string
}
/// The types are defined above without an interface
let row1 = {id = "Row1"; createdAt = DateTime.Now}
let row2 = {id = "Row2"; createdAt = DateTime.Now; address = "NYC"}
type IDBRow<'A> =
abstract member Data:(string * DateTime)
// Object expression implements the interface
let Data1 (x:DBRow1) = {
new IDBRow<_> with
member __.Data = (x.id, x.createdAt)
}
let Data2 (x: DBRow2) = {
new IDBRow<_> with
member __.Data = (x.id, x.createdAt)
}
//pass in both the object expression and the record
let getData (ifun: 'a -> IDBRow<'b>) xrec =
(ifun xrec).Data
// You could partially apply the functions: `getData1 = getData Data1`
getData Data1 row1 //("Row1", 2018/02/05 9:24:17)
getData Data2 row2 //("Row2", 2018/02/05 9:24:17)
You can certainly use an interface (an object expression in this case) to tack on another member, .Data, even if you don'T have access to the original type. You would still need to put together one object expression for each type though, so SRTP might be a more "elegant" solution.
Is there a way to name the function arguments in a constructor?
type UnnamedInCtor(foo: string -> string -> bool) =
member this.Foo: string -> string -> bool = foo
member this.Bar: a:string -> b:string -> bool = foo
member this.Fizz = foo
//Does not compile
type NamedInCtor(foo: a:string -> b:string -> bool) =
member this.Foo: string -> string -> bool = foo
member this.Bar: a:string -> b:string -> bool = foo
member this.Fizz = foo
I think that it's impossible in F#, however you can use type abbreviations if you want to document what foo represents:
// Compiles
type aToBToC = string -> string -> bool
type NamedInCtor(foo: aToBToC) =
member this.Foo: string -> string -> bool = foo
member this.Bar: a:string -> b:string -> bool = foo
member this.Fizz = foo
You would need to de-curry the function in your constructor:
type NamedInCtor(a, b) =
member this.Foo: string -> string -> bool = a b
member this.Bar: string -> string -> bool = a b
member this.Fizz = a b
Note that a and b are implicitly typed here. You should trust the compiler to do this as much as possible, because it makes your code much more readable.
Remember, functions are first class types and traditional objects are discouraged. What you're asking is essentially "can I name and access some arbitrary subset of this type?" The answer to that is no. If you want that behavior, then you must structure your functions to request it.
Testing out NoRM https://github.com/atheken/NoRM from F# and trying to find a nice way to use it. Here is the basic C#:
class products
{
public ObjectId _id { get; set; }
public string name { get; set; }
}
using (var c = Mongo.Create("mongodb://127.0.0.1:27017/test"))
{
var col = c.GetCollection<products>();
var res = col.Find();
Console.WriteLine(res.Count().ToString());
}
This works OK but here is how I access it from F#:
type products() =
inherit System.Object()
let mutable id = new ObjectId()
let mutable _name = ""
member x._id with get() = id and set(v) = id <- v
member x.name with get() = _name and set(v) = _name <- v
Is there an easier way to create a class or type to pass to a generic method?
Here is how it is called:
use db = Mongo.Create("mongodb://127.0.0.1:27017/test")
let col = db.GetCollection<products>()
let count = col.Find() |> Seq.length
printfn "%d" count
Have you tried a record type?
type products = {
mutable _id : ObjectId
mutable name : string
}
I don't know if it works, but records are often good when you just need a class that is basically 'a set of fields'.
Just out of curiosity, you can try adding a parameter-less constructor to a record. This is definitely a hack - in fact, it is using a bug in the F# compiler - but it may work:
type Products =
{ mutable _id : ObjectId
mutable name : string }
// Horrible hack: Add member that looks like constructor
member x.``.ctor``() = ()
The member declaration adds a member with a special .NET name that is used for constructors, so .NET thinks it is a constructor. I'd be very careful about using this, but it may work in your scenario, because the member appears as a constructor via Reflection.
If this is the only way to get succinct type declaration that works with libraries like MongoDB, then it will hopefuly motivate the F# team to solve the problem in the future version of the language (e.g. I could easily imagine some special attribute that would force F# compiler to add parameterless constructor).
Here is a pretty light way to define a class close to your C# definition: it has a default constructor but uses public fields instead of getters and setters which might be a problem (I don't know).
type products =
val mutable _id: ObjectId
val mutable name: string
new() = {_id = ObjectId() ; name = ""}
or, if you can use default values for your fields (in this case, all null):
type products() =
[<DefaultValue>] val mutable _id: ObjectId
[<DefaultValue>] val mutable name: string