Can you encapsulate multi case discriminated unions? - f#

I see that you can enforce constructor usage of single-case discriminated unions, can you do the same with multi-case?
for example
type MemberId =
| MemberId of int
| MemberGuid of Guid
I'm currently trying in the fsi like this
val create : int -> T option
val create : Guid -> T option
but I'm guessing like C#, F# won't allow you to overload based on return type for the unwrap:
val value : T -> string
Edit ---------------
MemberId.fsi =
module MemberId
open System
type _T
val createId : int -> _T option
val createGuid : Guid -> _T option
val value : _T -> 'a
MemberId.fs =
module MemberId
open System
type _T =
| Id of int
| MemberGuid of Guid
let createId id = match id with
| x when x>0 -> Some(Id(id))
| _ -> None
let createGuid guid = Some(MemberGuid( guid))
let value (e:_T):int = e
Appears to be pretty close, but the unwrapper doesn't compile and I can't seem to figure out how to write it
TestConsumer MemberIdClient.fs =
module MemberIdClient
open System
open MemberId
let address1 = MemberId.create(-1)
let address2 = MemberId.create(Guid.Empty)
let unwrapped1 =
match address1 with
| MemberId x -> () // compilation error on 'MemberId x'
| _ -> ()

Functions cannot be overloaded, but methods can:
type MemberId =
private
| MemberId of int
| MemberGuid of Guid
static member create id = MemberId id
static member create guid = MemberGuid guid

Indeed there is a way to overload with the output parameter, using some inline tricks:
open System
type MemberId =
private
| MemberId of int
| MemberGuid of Guid
type Create = Create with
static member ($) (Create, id ) = MemberId id
static member ($) (Create, guid) = MemberGuid guid
type Value = Value with
static member ($) (Value, d:int ) = function MemberId id -> id | _ -> failwith "Wrong case"
static member ($) (Value, d:Guid) = function MemberGuid guid -> guid | _ -> failwith "Wrong case"
let inline create x : MemberId = Create $ x
let inline value x : 'IntOrGuid = (Value $ Unchecked.defaultof<'IntOrGuid>) x
let a = create 1
let b = create (Guid.NewGuid())
let c:int = value a
let d:Guid = value b
By doing this you can 'overload' functions, even on output parameters.
Anyway the big difference with the single case DU is that now the unwrapper is not 'safe', that's why the unwrapper makes little sense, except in some specif scenarios.
In these cases you may consider other mechanisms to unwrap the values, like exposing functions isX or returning options which may be complemented with an active pattern to unwrap.
Having said that, if you are only interested in 'hiding' the constructors to do some validations, but not hiding the DU you can simply shadow the constructors, here's an example:
open System
type T =
| MemberId of int
| MemberGuid of Guid
// Shadow constructors
let MemberId x = if x > 0 then Some (MemberId x) else None
let MemberGuid x = Some (MemberGuid x)
let a = MemberId 1
let b = MemberGuid (Guid.NewGuid())
let c = MemberId -1
// but you can still pattern match
let printValue = function
| Some (MemberId x) -> sprintf "case 1, value is %A" x
| Some (MemberGuid x) -> sprintf "case 2, value is %A" x
| None -> "No value"
let ra = printValue a // "case 1, value is 1"
let rb = printValue b // "case 2, value is 67b36c20-2..."
let rc = printValue c // "No value"
// and if you want to use an overloaded constructor
type T with
static member Create id = MemberId id
static member Create guid = MemberGuid guid
let d = T.Create 1
let e = T.Create (Guid.NewGuid())
// or using the inline trick
type Create = Create with
static member ($) (Create, id ) = MemberId id
static member ($) (Create, guid) = MemberGuid guid
let inline create x : T option = Create $ x
let d' = create 1
let e' = create (Guid.NewGuid())

Here's the little bit of code from Gustavo's answer I needed that appears to work all by itself
module MemberId
open System
type MemberId =
| MemberId of int
| MemberGuid of Guid
// Shadow constructors
let MemberId x = if x > 0 then Some (MemberId x) else None
let MemberGuid x = Some (MemberGuid x)

Related

Continued "Is there an equivalent of C#'s nameof(..) in F#?"

With reference to Is there an equivalent of C#'s nameof(..) in F#?
how can the nameof function used or extended for the following case?
let nameof (q:Expr<_>) =
match q with
| Patterns.Let(_, _, DerivedPatterns.Lambdas(_, Patterns.Call(_, mi, _))) -> mi.Name
| Patterns.PropertyGet(_, mi, _) -> mi.Name
| DerivedPatterns.Lambdas(_, Patterns.Call(_, mi, _)) -> mi.Name
| _ -> failwith "Unexpected format"
let any<'R> : 'R = failwith "!"
let s = _nameof <# System.Char.IsControl #> //OK
type A<'a>() =
static member MethodWith2Pars(guid:Guid, str:string) = ""
static member MethodWith2Pars(guid:Guid, ba:byte[]) = ""
let s1 = nameof <# A<_>.MethodWith2Pars #> //Error FS0503 A member or object constructor 'MethodWith2Pars' taking 1 arguments is not accessible from this code location. All accessible versions of method 'MethodWith2Pars' take 2 arguments
let s2 = nameof <# A<_>.MethodWith2Pars : Guid * string -> string #> //Same error
The compiler gives the following error:
Error FS0503 A member or object constructor 'MethodWith2Pars' taking 1 arguments is not accessible from this code location. All accessible versions of method 'MethodWith2Pars' take 2 arguments
The answer you linked is a bit outdated. F# 5.0 (released just recently) offers the true nameof feature. See the announcement: https://devblogs.microsoft.com/dotnet/announcing-f-4-7/#nameof
This feature also existed in preview since F# 4.7: https://devblogs.microsoft.com/dotnet/announcing-f-4-7/#nameof
You can write your code like this:
open System
type A() =
static member MethodWith2Pars(guid:Guid, str:string) = ""
static member MethodWith2Pars(guid:Guid, ba:byte[]) = ""
let s1 = nameof (A.MethodWith2Pars : Guid * byte[] -> string)
let s2 = nameof (A.MethodWith2Pars : Guid * string -> string)
The type annotation is needed due to overloading. Not sure why there is a generic type parameter on the class declaration, but it's not used anywhere so I just removed it.

F# `This expression was expected to have type 'IDictionary<Type,obj>' but here has type 'Dictionary<Type,obj>'

I am trying to convert a C# class into F#:
type Aggregator<'T when 'T : (new : unit -> 'T)>()=
static let ApplyMethod = "Apply"
member val private _aggregations : IDictionary<Type, obj> = new Dictionary<Type, obj>()
member val AggregateType = typeof<'T> with get
member val Alias = Unchecked.defaultof<string> with get
However it seems that even this simple code cannot compile:
Program.fs:1189 This expression was expected to have type
'IDictionary<Type,obj>'
but here has type
'Dictionary<Type,obj>'
Does it mean that a field declared with an interface IDictionary<Type, obj> type cannot infer the value passed knowing that this particular value implements that interface Dictionary<Type, obj>?
Actually if I am explicitely upcasting to IDictionary<Type, obj>:
member val private _aggregations : IDictionary<Type, obj> =
(new Dictionary<Type, obj>() :> IDictionary<Type, obj>)
This works, does it mean that F# is stricter than C# in that regard?
As pointed in the comment, F# does require an explicit:
type Aggregator<'T when 'T : (new : unit -> 'T)>()=
static let ApplyMethod = "Apply"
member val private _aggregations : IDictionary<Type, obj> = new Dictionary<Type, obj>() :> IDictionary<Type, obj>)
member val AggregateType = typeof<'T> with get
member val Alias = Unchecked.defaultof<string> with get
Side notes:
Btw, not everything what you can do in C# is possible in F# (no protected access modifier for example).
Result of the conversion:
type Aggregator<'T when 'T : (new : unit -> 'T) and 'T : not struct> (overrideMethodLookup : IEnumerable<MethodInfo>)=
let aggregations : IDictionary<Type, obj> = (new Dictionary<Type, obj>() :> IDictionary<Type, obj>)
let aggregateType = typeof<'T>
let mutable alias = Unchecked.defaultof<string>
do
alias <- typeof<'T>.Name.ToTableAlias();
overrideMethodLookup.Each(fun (method : MethodInfo) ->
let mutable step = Unchecked.defaultof<obj>
let mutable eventType = method.GetParameters().Single<ParameterInfo>().ParameterType;
if eventType.Closes(typedefof<Event<_>>) then
eventType <- eventType.GetGenericArguments().Single();
step <- typedefof<EventAggregationStep<_,_>>.CloseAndBuildAs<obj>(method, [| typeof<'T>; eventType |]);
else
step <- typedefof<AggregationStep<_,_>>.CloseAndBuildAs<obj>(method, [| typeof<'T>; eventType |]);
aggregations.Add(eventType, step)
) |> ignore
static let ApplyMethod = "Apply"
new() = new Aggregator<'T>(typeof<'T>.GetMethods()
|> Seq.where (fun x -> x.Name = ApplyMethod &&
x.GetParameters().Length = 1))
member this.Add<'TEvent>(aggregation: IAggregation<'T, 'TEvent>) =
if aggregations.ContainsKey(typeof<'TEvent>) then
aggregations.[typeof<'TEvent>] <- aggregation
else
aggregations.Add(typeof<'TEvent>, aggregation)
this
member this.Add<'TEvent>(application: Action<'T, 'TEvent>) =
this.Add(new AggregationStep<'T, 'TEvent>(application));
interface IAggregator<'T> with
member this.AggregatorFor<'TEvent>() =
if aggregations.ContainsKey(typeof<'TEvent>) then
aggregations.[typeof<'TEvent>].As<IAggregation<'T, 'TEvent>>()
else
null
member this.Build(events, session, state) =
events.Each(fun (x : IEvent) -> x.Apply(state, this)) |> ignore
state
member this.Build(events, session) =
(this :> IAggregator<'T>).Build(events, session, new 'T());
member this.EventTypes =
aggregations.Keys.ToArray();
member this.AggregateType =
aggregateType
member this.Alias =
alias
member this.AppliesTo(stream) =
stream.Events.Any(fun x -> aggregations.ContainsKey(x.Data.GetType()));

this expression was expected to have type IDataReader but here has type SqlDataReader

The following code is not casting my return value of SqlDataReader from getReader correctly to IDataReaderin the call to Seq.unfold. What am I doing wrong?
open System.Data
open System.Data.SqlClient
open System.Configuration
type Foo = { id:int; name:string }
let populateFoo (r:IDataReader) =
let o = r.GetOrdinal
{ id = o "id" |> r.GetInt32; name = o "name" |> r.GetString; }
let iter populateObject (r:IDataReader) =
match r.Read() with
| true -> Some(populateObject r, r)
| _ -> None
let iterFoo = iter populateFoo
let getReader : IDataReader =
let cnstr = ConfigurationManager.ConnectionStrings.["db"].ConnectionString
let cn = new SqlConnection(cnstr)
let cmd = new SqlCommand("select * from Foo", cn)
cmd.ExecuteReader()
let foos = Seq.unfold iterFoo getReader
F# does not automatic upcasting like C#, except in some specific scenarios (see the spec, section 14.4.2).
You have to explicitly cast the expression: cmd.ExecuteReader() :> IDataReader then you can remove the type annotation after getReader.
Alternatively you may leave that function returning an SqlDataReader and upcast at the call site:
let foos = getReader :> IDataReader |> Seq.unfold iterFoo
If unfold was a static member of a type with a signature like this one:
type T() =
static member unfold(a, b:IDataReader) = Seq.unfold a b
you would be able to do directly T.unfold(iterFoo, getReader) and it will automatically upcast. That's one of the cases mentioned in the spec.

Type inference failure with F# gives an unclear error

I have the following ViewModelBase in F# which I'm trying to build to learn F# with WPF.
module MVVM
open System
open System.Collections.ObjectModel
open System.ComponentModel
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open System.Reactive.Linq
module Property =
let ToName(query : Expr) =
match query with
| PropertyGet(a, b, list) ->
b.Name
| _ -> ""
let SetValue<'t>(obj, query : Expr<'t>, value : 't) =
match query with
| PropertyGet(a, b, list) ->
b.SetValue(obj, value)
| _ -> ()
let GetValue<'o, 't>(obj : 'o , query : Expr<'t>) : option<'t> =
match query with
| PropertyGet(a, b, list) ->
option.Some(b.GetValue(obj) :?> 't )
| _ -> option.None
let Observe<'t>(x: INotifyPropertyChanged) (p : Expr<'t>) =
let name = ToName(p)
x.PropertyChanged.
Where(fun (v:PropertyChangedEventArgs) -> v.PropertyName = name).
Select(fun v -> GetValue(x, p).Value)
type ViewModelBase() =
let propertyChanged = new Event<_, _>()
interface INotifyPropertyChanged with
[<CLIEvent>]
member x.PropertyChanged = propertyChanged.Publish
abstract member OnPropertyChanged: string -> unit
default x.OnPropertyChanged(propertyName : string) =
propertyChanged.Trigger(x, new PropertyChangedEventArgs(propertyName))
member x.SetValue<'t>(expr : Expr<'t>, v : 't) =
Property.SetValue(x, expr, v)
x.OnPropertyChanged(expr)
member x.OnPropertyChanged<'t>(expr : Expr<'t>) =
let propName = Property.ToName(expr)
x.OnPropertyChanged(propName)
However I get an error from the compiler
Error 1 The type 'ViewModelBase' is used in an invalid way.
A value prior to 'ViewModelBase' has an inferred type involving
'ViewModelBase', which is an invalid forward reference.
However the compiler doesn't tell me what value prior is the offending part of the problem. As I'm pretty new to the type inference as used by F# I'm probably missing an obvious problem.
FYI the code is meant to be used like the below but at the moment this code is commented out and the error is only pertaining to the core code above
type TestModel() as this =
inherit MVVM.ViewModelBase()
let mutable name = "hello"
let subscription = (Property.Observe this <# this.SelectedItem #>).
Subscribe(fun v -> Console.WriteLine "Yo")
member x.SelectedItem
with get() = name
and set(v) =
x.SetValue(<# x.SelectedItem #>, v)
I found it.
let SetValue<'t>(obj, query : Expr<'t>, value : 't) =
match query with
| PropertyGet(a, b, list) ->
b.SetValue(obj, value)
| _ -> ()
was under constrained. Should be
let SetValue<'t>(obj : Object, query : Expr<'t>, value : 't) =
match query with
| PropertyGet(a, b, list) ->
b.SetValue(obj, value)
| _ -> ()

f# pattern matching with types

I'm trying to recursively print out all an objects properties and sub-type properties etc. My object model is as follows...
type suggestedFooWidget = {
value: float ;
hasIncreasedSinceLastPeriod: bool ;
}
type firmIdentifier = {
firmId: int ;
firmName: string ;
}
type authorIdentifier = {
authorId: int ;
authorName: string ;
firm: firmIdentifier ;
}
type denormalizedSuggestedFooWidgets = {
id: int ;
ticker: string ;
direction: string ;
author: authorIdentifier ;
totalAbsoluteWidget: suggestedFooWidget ;
totalSectorWidget: suggestedFooWidget ;
totalExchangeWidget: suggestedFooWidget ;
todaysAbsoluteWidget: suggestedFooWidget ;
msdAbsoluteWidget: suggestedFooWidget ;
msdSectorWidget: suggestedFooWidget ;
msdExchangeWidget: suggestedFooWidget ;
}
And my recursion is based on the following pattern matching...
let rec printObj (o : obj) (sb : StringBuilder) (depth : int)
let props = o.GetType().GetProperties()
let enumer = props.GetEnumerator()
while enumer.MoveNext() do
let currObj = (enumer.Current : obj)
ignore <|
match currObj with
| :? string as s -> sb.Append(s.ToString())
| :? bool as c -> sb.Append(c.ToString())
| :? int as i -> sb.Append(i.ToString())
| :? float as i -> sb.Append(i.ToString())
| _ -> printObj currObj sb (depth + 1)
sb
In the debugger I see that currObj is of type string, int, float, etc but it always jumps to the defualt case at the bottom. Any idea why this is happening?
As others has pointed out, you need to invoke the GetValue member to get the value of the property - the iteration that you implemented iterates over PropertyInfo objects, which are "descriptors of the property" - not actual values. However, I don't quite understand why are you using GetEnumerator and while loop explicitly when the same thing can be written using for loop.
Also, you don't need to ignore the value returned by the sb.Append call - you can simply return it as the overall result (because it is the StringBuilder). This will actually make the code more efficient (because it enables tail-call optimizataion). As a last point, you don't need ToString in sb.Append(..), because the Append method is overloaded and works for all standard types.
So after a few simplification, you can get something like this (it's not really using the depth parameter, but I guess you want to use it for something later on):
let rec printObj (o : obj) (sb : StringBuilder) (depth : int) =
let props = o.GetType().GetProperties()
for propInfo in props do
let propValue = propInfo.GetValue(o, null)
match propValue with
| :? string as s -> sb.Append(s)
| :? bool as c -> sb.Append(c)
| :? int as i -> sb.Append(i)
| :? float as i -> sb.Append(i)
| _ -> printObj currObj sb (depth + 1)
Here is how I got it to work...
let getMethod = prop.GetGetMethod()
let value = getMethod.Invoke(o, Array.empty)
ignore <|
match value with
| :? float as f -> sb.Append(f.ToString() + ", ") |> ignore
...
In your example, enumer.Current is an object containing a PropertyInfo. This means that currObj is always a PropertyInfo object, and will always correspond to the last case in your match statement.
Since you're interested in the type of the value of the property, you'll need to call the GetValue() method of the PropertyInfo to get to the actual value of the property (as in ChaosPandion's answer).
Since an Enumerator returns its values as objects, you'll also need to cast the enum.current to a PropertyInfo before you can access GetValue.
Try replacing
let currObj = (enumer.Current : obj)
with
let currObj = unbox<PropertyInfo>(enumer.Current).GetValue (o, null)
With this change, I can get your code to work (in FSI):
> let test = {authorId = 42; authorName = "Adams"; firm = {firmId = 1; firmName = "GloboCorp inc."} };;
> string <| printObj test (new StringBuilder()) 1;;
val it : string = "42Adams1GloboCorp inc."
Are you sure the program is not behaving as expected? The debugger spans are not always reliable.
You want something like this instead.
let rec printObj (o : obj) (sb : StringBuilder) (depth : int)
let props = o.GetType().GetProperties() :> IEnumerable<PropertyInfo>
let enumer = props.GetEnumerator()
while enumer.MoveNext() do
let currObj = (enumer.Current.GetValue (o, null)) :> obj
ignore <|
match currObj with
| :? string as s -> sb.Append(s.ToString())
| :? bool as c -> sb.Append(c.ToString())
| :? int as i -> sb.Append(i.ToString())
| :? float as i -> sb.Append(i.ToString())
| _ -> printObj currObj sb (depth + 1)
sb
This is coming from the MSDN docs on the Array class:
In the .NET Framework version 2.0, the
Array class implements the
System.Collections.Generic.IList,
System.Collections.Generic.ICollection,
and
System.Collections.Generic.IEnumerable
generic interfaces. The
implementations are provided to arrays
at run time, and therefore are not
visible to the documentation build
tools. As a result, the generic
interfaces do not appear in the
declaration syntax for the Array
class, and there are no reference
topics for interface members that are
accessible only by casting an array to
the generic interface type (explicit
interface implementations). The key
thing to be aware of when you cast an
array to one of these interfaces is
that members which add, insert, or
remove elements throw
NotSupportedException.

Resources