F# Discriminated Union Type Issue - f#

I'm having a problem getting my DU working as expected. I've defined a new DU which either has a result of type <'a> or any Exception derived from System.Exception
open System
// New exceptions.
type MyException(msg : string) = inherit Exception(msg)
type MyOtherException(msg : string) = inherit MyException(msg)
// DU to store result or an exception.
type TryResult<'a, 't> =
| Result of 'a
| Error of 't :> Exception
//This is fine.
let result = Result "Test"
// This works, doing it in 2 steps
let ex = new MyOtherException("Some Error")
let result2 = Error ex
// This doesn't work. Gives "Value Restriction" error.
let result3 = Error (new MyOtherException("Some Error"))
I can't understand why it is allowing me to create an "Error" if I do it in 2 steps, but when i'm doing the same thing on a single line, I get a Value Restriction error.
What am i missing?
Thanks
UPDATE
Looking at the post by #kvb, adding type information each time I need to create an Error seemed a bit verbose, so I wrapped it up into an additional method which creates an Error and is a bit more succinct.
// New function to return a Result
let asResult res : TryResult<_,Exception> = Result res
// New function to return an Error
let asError (err : Exception) : TryResult<unit,_> = Error(err)
// This works (as before)
let myResult = Result 100
// This also is fine..
let myResult2 = asResult 100
// Using 'asError' now works and doesn't require any explicit type information here.
let myError = asError (new MyException("Some Error"))
I'm not sure if specifying an Error with 'unit' will have any consequences I haven't foreseen yet.
TryResult<unit,_> = Error(err)

Consider this slight variation:
type MyOtherException(msg : string) =
inherit MyException(msg)
do printfn "%s" msg
let ex = new MyOtherException("Some Error") // clearly, side effect occurs here
let result2 = Error ex // no side effect here, but generalized value
let intResults = [Result 1; result2]
let stringResults = [Result "one"; result2] // can use result2 at either type, since it's a generalized value
let result3 = Error (MyOtherException("Some Error")) // result would be of type TryResult<'a, MyOtherException> for any 'a
// In some other module in a different compilation unit
let intResults2 = [Result 1; result3] // why would side effect happen here? just using a generic value...
let stringResults2 = [Result "one"; result3] // likewise here...
The issue is that it looks like result3 is a value, but the .NET type system doesn't support generic values, it only supports values of concrete types. Therefore, the MyOtherException constructor needs to be called each time result3 is used; however, this would result in any side effects occurring more than once, which would be surprising. As Ringil suggests, you can work around this by telling the compiler to treat the expression as a value anyway:
[<GeneralizableValue>]
let result3<'a> : TryResult<'a,_> = Error(new MyOtherException("Some Error"))
This is fine as long as the constructor doesn't have side effects.

You can do:
let result3<'a> = Error (new MyOtherException("Some Error"))
EDIT:
As for why you can't do it in one step, first note that this results in the same error:
let result4 = Result (new MyOtherException("Some Error"))
As does this:
let result4 = Result ([|1;|])
But that this works:
let result4 = Result ([1;])
What's similar about Exception and Arrays, but not Lists? It's their mutability. The value restriction will bother you when you try to do make a TryResult with a type that is mutable in a single step.
Now as for why the two step process solves this, it's because the constructor make the whole function not generalizable because you're applying a function to the constructor. But splitting it into two steps solves that. It is similar to Case 2 here on MSDN.
You can read more about it at the above MSDN article and the why this happens in this more indepth blog post.

Related

Compiler unable to resolve type from shared 'header' file

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)

How to upcast to an option type?

How do I create some string from a string?
I'm going through the F# Koans tutorials, and am I stuck on this one:
[<Koan>]
let ProjectingValuesFromOptionTypes() =
let chronoTrigger = { Name = "Chrono Trigger"; Platform = "SNES"; Score = Some 5 }
let halo = { Name = "Halo"; Platform = "Xbox"; Score = None }
let decideOn game =
game.Score
|> Option.map (fun score -> if score > 3 then "play it" else "don't play")
//HINT: look at the return type of the decide on function
AssertEquality (decideOn chronoTrigger) (Some "play it")
AssertEquality (decideOn halo) (Some "don't play")
the exception i get is:
You have not yet reached enlightenment ...
Expected: null
But was: <Some(don't play)>
How do I upcast a string to be of type option string?
How do I upcast a string to be of type option string?
Casting has a very specific meaning. What you want to do is wrap your string with an Option, not cast it. To do this, use the Some constructor function:
let x = Some myString //x: string option
However, I don't think that is going to fix assertion error you're getting (at least, not by itself). I don't want to give you the complete answer here (especially since that's not what you're asking and finding the answer is the entire point of doing a Koan) but I will leave this clue as to why you're seeing a null in the assertion:
None |> printfn "Value: %A" // Value: <null>
See Why is None represented as null? for some more information on that behavior.

F# Adding value to map result in KeyNotFoundException

type bytesLookup = Map<byte,int list>
type lookupList = bytesLookup list
let maps:bytesLookup = Map.empty
let printArg arg = printfn(Printf.TextWriterFormat<unit>(arg))
let array1 = [|byte(0x02);byte(0xB1);byte(0xA3);byte(0x02);byte(0x18);byte(0x2F)|]
let InitializeNew(maps:bytesLookup,element,index) =
maps.Add(element,List.empty<int>)(*KeyNotFoundException*)
maps.[element]
let MapArray (arr:byte[],maps:bytesLookup ) =
for i in 0..arr.Length do
match maps.TryFind(arr.[i]) with
| Some(e) -> i::e
| None -> InitializeNew(maps,arr.[i],i)
MapArray(array1,maps);
printArg( maps.Count.ToString())
Exception
System.Collections.Generic.KeyNotFoundException: The given key was not
present in the dictionary. at
Microsoft.FSharp.Collections.MapTreeModule.find[TValue,a](IComparer1
comparer, TValue k, MapTree2 m) at
Microsoft.FSharp.Collections.FSharpMap2.get_Item(TKey key) at
FSI_0012.MapArray(Byte[] arr, FSharpMap2 maps) in Script1.fsx:line 16
at .$FSI_0012.main#() in Script1.fsx:line 20
In the function I'm trying to initialize a new element in the map with a list of int. I also try to push a new int value into the list at the same time.
What am I doing wrong?
F# Map is an immutable data structure, the Add method doesn't modify the existing data structure, it returns a new Map with the additions you've requested.
Observe:
let ex1 =
let maps = Map.empty<byte, int list>
maps.Add(1uy, [1]) // compiler warning here!
maps.[1uy]
Two things about this code:
It throws System.Collections.Generic.KeyNotFoundException when you run it
It gives you a compiler warning that the line maps.Add... should have type unit but actually has type Map<byte,int list>. Don't ignore the warning!
Now try this:
let ex2 =
let maps = Map.empty<byte, int list>
let maps2 = maps.Add(1uy, [1])
maps2.[1uy]
No warning. No exception. Code works as expected, returning the value [1].

Type annotation required

I have the following:
let GetDateTime() = System.DateTime.UtcNow
let InternalHandle dependencies =
let getDateTime = dependencies
let future = getDateTime()
let future = getDateTime().AddDays(float 5)
printf "The time is %A" future
()
let Handle() =
let dependencies = (GetDateTime)
InternalHandle dependencies
Handle();;
but I am compiler error with "getDateTime().AddDays(float 5)" regarding type annotation.
What do I need to do to get this to work?
getDateTime is a function and I cant add a type annotation.
I am baffled why it does not pick up its a function that returns a DateTime and therefore all functions like AddDays would be available
When doing type inference on functions, the F# compiler does not look at how the function is called later on. This means that when inferring type for InternalHandle, it only sees this:
let InternalHandle dependencies =
let getDateTime = dependencies
let future = getDateTime() // (#1)
let future = getDateTime().AddDays(float 5) // (#2)
printf "The time is %A" future
From this, it can infer that dependencies is a function (because it is called in #1), but it does not know what the function returns. When it gets to (#2), it sees that you want to invoke AddDays method on the result - but this is not enough to decide what the result type is (it could be any .NET object that has this method...). For this reason, you need a type annotation to specify what object are you expecting. You can write:
let InternalHandle (dependencies : unit -> System.DateTime) =
let getDateTime = dependencies
let future = getDateTime()
let future = getDateTime().AddDays(float 5)
printf "The time is %A" future

How to build WriteBuf from array

I am serializing two values in to an array and I am trying to go through a WriteBuf but I am getting the error that
error: the trait `std::io::Write` is not implemented for the type `[_; 12]`
error: type `std::io::buffered::BufWriter<&mut [_; 12]>` does not implement any method in scope named `write_be_u32`
error: type `std::io::buffered::BufWriter<&mut [_; 12]>` does not implement any method in scope named `write_be_f64`
Here is the minimum code to generate this error:
use std::io::{ BufWriter, Write };
fn main(){
let packed_data = [0; 12];
let timestamp : u32 = 100;
let value : f64 = 9.9;
let writer = BufWriter::new(&mut packed_data);
writer.write_be_u32(timestamp);
writer.write_be_f64(value);
println!("Packed data looks like {:?}", packed_data);
}
Am I no borrowing the slice correctly? Am I note using the proper module to define the Write trait for my buffer?
Here is a playpen for this code: http://is.gd/ol8qND
I see a few potential problems with your code:
packed_data isn't mutable.
You use packed_data at the end of main while writer holds a mutable reference to it.
I don't think that either of those things are causing the error. I did however find something that works:
use std::io::{ BufWriter, Write };
fn main() {
let mut packed_data = [0; 12];
{
let packed_data_ref: &mut [u8] = &mut packed_data;
let mut writer = BufWriter::new(packed_data_ref);
writer.write(&[1, 2, 3, 4]).unwrap();
} // `writer` gets deallocated and releases the mutable reference
println!("Packed data looks like {:?}", packed_data);
}
[playpen]
So I guess the issue is that you need a &[u8] rather than a &[u8; 12]. I have no idea why. I hope this at least helps though.

Resources