I'm using ROP (Railway Oriented Programming), modeling it after swlaschin in a SQLite wrapper:
And I am returning RopResult types:
type RopResult<'TSuccess, 'TMessage> =
| Success of 'TSuccess * 'TMessage list
| Failure of 'TMessage list
And all works fine in F# in composition, but I still need to interop to C# sometimes and in returning a RopResult things fall apart. ie:
RopResult<seq 't, DomainMessage>
How can I access those "properties" (ie. the C#'s ienumerable) without having to resort to casting it as a dynamic:
var ropResult = dao.All; // RopResult<seq 't, DomainMessage>
var dyn = ropResult as dynamic;
foreach (SomeClass item in dyn.Item1)
{
Console.WriteLine(item.Id);
}
You can simply cast to the relevant subtype or use pattern matching (in C# 7.0 or later):
switch (result)
{
case RopResult<IEnumerable<T>, DomainMessage>.Success success:
var value = success.Item1;
var messages = success.Item2;
// do something
break;
case RopResult<IEnumerable<T>, DomainMessage>.Failure failure:
var errors = failure.Item;
// do something else
break;
default:
throw new ArgumentOutOfRangeException();
}
However, this isn't an ideal API from C#. You might want to write some extension methods to help with this. You might get some inspiration from Chessie.
Related
I am new to F# and am writing a function to convert a dateTime value to unix timestamp in seconds.
Below is my implementation of such a function (just using builtin function and wrapping it for learning).
let DateTimeToUnix x =
let result = DateTimeOffset(x).ToUnixTimeSeconds
result
Expected return type is: DateTime -> int64
Actual return type is: DateTime-> (unit->int64)
I tried to find similar questions and the closest i found was something like this (F# - The type int is not compatible with type unit)
but unfortunately i don't understand how it is applicable in this case since the return type of the function (ToUnixTimeSeconds) used is int64 and not (unit->int64).
It would be great if someone can point to where to learn about this or can explain the behavior. Thanks a lot for your time.
ToUnixTimeSeconds is a method, not a property. In F# methods are modeled as functions. Parameterless methods are modeled as functions with a single unit-typed parameter. So the type of foo.ToUnixTimeSeconds would be unit -> int64, just like the compiler is telling you.
To actually call that function and obtain a result from it, give it a parameter of the type that it expects - i.e. unit:
let DateTimeToUnix x =
let result = DateTimeOffset(x).ToUnixTimeSeconds ()
result
As a side note, you don't really have to name the intermediate value result:
let DateTimeToUnix x =
DateTimeOffset(x).ToUnixTimeSeconds ()
You have to invoke ToUnixTimeSeconds before returning it in order to get the desired return type:
let DateTimeToUnix x =
let result = DateTimeOffset(x).ToUnixTimeSeconds() // invoke method: ()
result
This is a following to: deserialization issue, with json.net, in F#.
I am deserializing some JSON that has an extra, unbound property using FSharpLu.Json. Here is the code:
open System
open Newtonsoft.Json
open Microsoft.FSharpLu.Json
type r =
{
a: int
}
let a =
"{\"a\":3, \"b\":5}"
Compact.TupleAsArraySettings.settings.MissingMemberHandling <- MissingMemberHandling.Ignore
Compact.deserialize<r> a // doesn't work
Despite setting MissingMemberHandling.Ignore it returns a json.net error:
Could not find member 'b' on object of type 'r'. Path 'b', line 1, position 13.
Is there a way to make this work, or is it an issue with FSharpLu.Json?
Here is the fiddle: https://dotnetfiddle.net/OsVv1M
as a side note, there is another deserializer in FSharpLu.Json and I can get that code to work with it:
FSharpLu.Json.Default.Internal.DefaultSettings.settings.MissingMemberHandling <- MissingMemberHandling.Ignore
Default.deserialize<r> a
will work, but that deserializer doesn't handle discriminated unions... so I need to get the compact one to work.
When looking into the source of FSharpLu.Json, I found this:
/// Compact serialization where tuples are serialized as heterogeneous arrays
type TupleAsArraySettings =
static member formatting = Formatting.Indented
static member settings =
JsonSerializerSettings(
NullValueHandling = NullValueHandling.Ignore,
// MissingMemberHandling is not technically needed for
// compact serialization but it avoids certain ambiguities
// that guarantee that deserialization coincides with the
// default Json.Net deserialization.
// (where 'coincides' means 'if the deserialization succeeds they both return the same object')
// This allows us to easily define the BackwardCompatible
// serializer (that handles both Compact and Default Json format) by reusing
// the Compact deserializer.
MissingMemberHandling = MissingMemberHandling.Error,
Converters = [| CompactUnionJsonConverter(true, true) |]
)
so they are explicitly setting the MissingMemberHandling to Error; maybe the solution is to instantiate the deserializer, change the setting and then use it.
The serializer setting you are trying to mutate, Compact.TupleAsArraySettings.settings, is a static member, as is shown in the code:
type TupleAsArraySettings =
static member formatting = Formatting.Indented
static member settings =
JsonSerializerSettings(
NullValueHandling = NullValueHandling.Ignore,
// MissingMemberHandling is not technically needed for
// compact serialization but it avoids certain ambiguities
// that guarantee that deserialization coincides with the
// default Json.Net deserialization.
// (where 'coincides' means 'if the deserialization succeeds they both return the same object')
// This allows us to easily define the BackwardCompatible
// serializer (that handles both Compact and Default Json format) by reusing
// the Compact deserializer.
MissingMemberHandling = MissingMemberHandling.Error,
Converters = [| CompactUnionJsonConverter(true, true) |]
)
Since a member is actually a member function (i.e. a method) as explained F# for fun and profit: Attaching functions to types, settings is actually (in c# terminology) a static property returning a new instance of JsonSerializerSettings each time it is invoked. To test this, we can do:
printfn "%b" (Object.ReferenceEquals(Compact.TupleAsArraySettings.settings, Compact.TupleAsArraySettings.settings)) // prints "false"
Which prints "false". Thus mutating the returned value has no effect on the behavior of Compact. A static field in the c# sense would be defined by a static let statement; if settings returned such a field, mutating its contents would have had an effect.
In any event modifying the value of Compact.TupleAsArraySettings.settings.MissingMemberHandling seems unwise as doing so would modify the behavior of of Compact.deserialize throughout your entire AppDomain in a way that might break backwards compatibility with Json.NET native serialization. As explained in the code comments above, the setting is required to make BackwardCompatible.deserialize work. But why might that be? Since Json.NET's native format for option and discriminated unions looks like:
{
"a": {
"Case": "Some",
"Fields": [
3
]
}
}
We can guess that MissingMemberHandling is used to trap situations where "Case" and "Fields" are found or not, and switch from one algorithm to the other.
If you are certain you do not need to deserialize f# types in Json.NET format, it seems you should be able to use CompactUnionJsonConverter directly as follows:
let settings = JsonSerializerSettings(
NullValueHandling = NullValueHandling.Ignore,
Converters = [| CompactUnionJsonConverter(true, true) |]
)
let c = JsonConvert.DeserializeObject<r>(a, settings)
let json2 = JsonConvert.SerializeObject(c, Formatting.Indented, settings)
Demo fiddle here.
In Common.fs:
namespace Bug
module Common =
type SourceEntity = {
id : int
link : string
}
type ReleaseEntity = {
id : int
notes : string
}
In Release.fs
namespace Bug
open System
open Common
module Release =
let cache = new Collections.Generic.Dictionary<int, ReleaseEntity>()
let AddToCache(entity) =
cache.Add(entity.id, entity)
()
let AddRec() =
let entity : ReleaseEntity = {
id = 1
notes = "Notes"
}
AddToCache(entity)
In Source.fs
namespace Bug
open System
open Common
module Source =
let Cache = new Collections.Generic.Dictionary<int, SourceEntity>()
let AddToCache(entity) =
Cache.Add(entity.id, entity) <<=== E R R O R
()
let AddRec() =
let ent : SourceEntity = {
id = 1
releases = "Releases"
}
AddToCache(ent) <<=== E R R O R
Files included in above order in the Visual Studio project.
Error reported in Source.fs:
Error FS0001 This expression was expected to have type
'SourceEntity'
but here has type
'ReleaseEntity'
If the order of the two types in Common.fs are reversed, the error is reported in Release.fs where expected type is ReleaseEntity but has type SourceEntity.
Any ideas why this error is happening?
It's a clash (and shadowing) of record field names.
When you write entity.id in the body of Bug.Source.AddToCache, the compiler uses the fact that you're accessing the .id field to infer the type of entity. Which records have a field named id? Well, those two records do, but the compiler has to pick one. How? Easy: the last one takes precedence. This is called "shadowing".
In order to disambiguate the choice, just add a type annotation:
let AddToCache(entity) =
Cache.Add(entity.id, entity)
()
Wait, but why doesn't the compiler use the type of Cache.Add to infer the type of entity?
Well, this is just a limitation (or a feature?) of F#. The compilation is single-pass, type interference proceeds top down, left to right, without doublebacks. This allows the compiler to be very fast and very predictable (looking at you, Haskell).
But in this case it means that by the time the compiler sees that entity is used as parameter in Cache.Add, it has already decided what its type must be.
When you get a type error, try to think about how the compiler came to infer that particular type.
Here:
let AddToCache(entity) =
cache.Add(entity.id, entity)
()
Could the compiler know which type has would have an id field in entity?
If you had typed in
let entity = { id = 1; link = "" }
the compiler would infer that this is SourceEntity because only SourceEntity has those particular record fields. In cache.Add(entity.id, entity), the compiler has no other constraints to go by, other than it has to have an id field, so it picks the last matching type - and that is why you get the error.
If you refactor the common id field to
namespace Bug
module Common =
type SourceEntity = {
source_id : int
link : string
}
type ReleaseEntity = {
release_id : int
notes : string
}
you will find that the error disappears.
Solutions
All of the solutions involve constraining it to a known type.
The simplest is to add a type annotation:
let AddToCache(entity: SourceEntity) =
Another is to deconstruct it explicitly:
let { SourceEntity.id = id } = entity
Cache.Add(id, entity)
Another is to coerce the type - this isn't relevant here, but it may come to be useful down the road:
Cache.Add((entity :> SourceEntity).id, entity)
I'd recommend this article from F# for fun and profit on type inference for a nice explanation of the process.
P.S.
You actually only needed that one type annotation.
The rest can be inferred :)
module Source =
let Cache = new Collections.Generic.Dictionary<_, _>()
let AddToCache (entity: SourceEntity) =
Cache.Add(entity.id, entity)
()
let AddRec () =
let ent = {
id = 1
link = ""
}
AddToCache(ent)
I am trying to write a function that takes two arguments: givenType and targetType. If these two arguments match, I want givenType to be returned, otherwise null.
For this objective, I am trying to utilize Dart's is expression (maybe there is a better way to go about it, I am open to suggestions). Initially, I thought it would be as simple as writing this:
matchesTarget(givenType, targetType) {
if (givenType is targetType) {
return givenType;
}
return null;
}
But this produces an error:
The name 'targetType' isn't a type and can't be used in an 'is'
expression. Try correcting the name to match an existing
type.dart(type_test_with_non_type)
I tried looking up what satisfies an is expression but cannot seem to find it in the documentation. It seems like it needs its right-hand operand to be known at compile-time (hoping this is wrong, but it does not seem like I can use a variable), but if so, how else can I achieve the desired effect?
I cant guess the purpose of the function (or the scenario where it would be used, so if you can clarify it would be great). First of all, I don't know if you are passing "types" as arguments. And yes, you need to specify in compile time the right hand argument of the is function.
Meanwhile, if you are passing types, with one change, you can check if the types passed to your function at runtime.
matchesTarget(Type givenType, Type targetType) {
print('${givenType.runtimeType} ${targetType.runtimeType}');
if (givenType == targetType) {
return givenType;
}
return null;
}
main(){
var a = int; //this is a Type
var b = String; //this is also a Type
print(matchesTarget(a,b)); //You are passing different Types, so it will return null
var c = int; //this is also a Type
print(matchesTarget(a,c)); //You are passing same Types, so it will return int
}
But if you are passing variables, the solution is pretty similar:
matchesTarget(givenVar, targetVar) {
print('${givenVar.runtimeType} ${targetVar.runtimeType}');
if (givenVar.runtimeType == targetVar.runtimeType) {
return givenVar.runtimeType;
}
return null;
}
main(){
var a = 10; //this is a variable (int)
var b = "hello"; //this is also a variable (String)
print(matchesTarget(a,b)); //this will return null
var c = 12; //this is also a variable (int)
print(matchesTarget(a,c)); //this will return int
}
The Final Answer
matchesTarget(givenVar, targetType) {
print('${givenVar.runtimeType} ${targetType}');
if (givenVar.runtimeType == targetType) {
return givenVar;
}
return null;
}
main(){
var a = 10; //this is a variable (int)
var b = String; //this is a type (String)
print(matchesTarget(a,b)); //this will return null because 'a' isnt a String
var c = int; //this is also a type (int)
print(matchesTarget(a,c)); //this will return the value of 'a' (10)
}
The as, is, and is! operators are handy for checking types at runtime.
The is operator in Dart can be only used for type checking and not checking if two values are equal.
The result of obj is T is true if obj implements the interface specified by T. For example, obj is Object is always true.
See the below code for an example of how to use the is operator
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
Even the error message that you're getting says that
The name 'targetType' isn't a type and can't be used in an 'is'
expression.
So the bottomline is that you can use is only for checking if a variable or value belongs to a particular data type.
For checking equality, you can use the == operator if comparing primitive types, or write your own method for comparing the values. Hope this helps!
I am writing a function that needs to return an array of items. In the function there will be some logic that will determine the type of the items I want to return from the function. I started something like this:
func testFunc<T>(option:Int) -> [T] {
var result:[T] = []
switch option {
case 1:
let sampleString:String = "hello"
result.append(sampleString)
case 2:
let sampleInt:Int = 23
result.append(sampleInt)
default:
return result
}
return result
}
This gives me the following errors:
"cannot invoke append with an argument list of type '(String)',
and
"cannot invoke append with an argument list of type '(Int)'
It makes sense, but, I am trying to figure out how to solve my problem. When I call the function I don't know what type will be in the array returned, but the function will know how to determine the type before it start appending items to the array.
How can I accomplish this in Swift?
Swift does not support variables with multiple types. Using a generic T makes the function generic so the return type can be chosen by the caller.
You should rethink your design.
If you really want to return multiple result types based on a parameter, there are a few workarounds:
1: Use an Any array.
var result:[Any] = []
2: Use an enum with associated values
enum TestFuncResult {
case string(String)
case int(Int)
}
func testFunc(option: Int) -> [TestFuncResult] {
var result:[TestFuncResult] = []
switch option {
case 1:
let sampleString:String = "hello"
result.append(.string(sampleString))
case 2:
let sampleInt:Int = 23
result.append(.int(sampleInt))
default:
return result
}
return result
}