Documenting F# Code - f#

In a C# class with a single constructor, I can add class summary XML documentation and constructor XML documentation:
///<summary>
///This class will solve all your problems
///</summary>
public class Awesome
{
/// <summary>
/// Initializes a new instance of the <see cref="Awesome"/> class.
/// </summary>
/// <param name="sauce">The secret sauce.</param>
public Awesome(string sauce)
{
//...implementation elided for security purposes
}
}
How do I do the same with the equivalent F# class such that the generated documentation is the same?
type Awesome(sauce: string) =
//...implementation elided for security purposes
CLARIFICATION: I'm aware that the standard XML documentation tags can be used in F#. My question is how to add them to the above snippet so that both the type and the constructor are documented.

I looked at the source of the open-source F# compiler and I think Dr_Asik is right - there is no way of documenting the implicit constructor with an XML comment. The node that represents the implicit constructor in the AST (See ImplicitCtor in ast.fs here) does not include a field for stroing the XML documentation (represented as PreXmlDoc type).
You can still document all public API - you'd have to use the method that Dr_Asik mentioned and mark the implicit constructor as private. I agree this is a bit ugly, but I think it is more convenient than not using implicit constructors:
type MyType private(a:int, u:unit) =
/// <summary>Creates MyType</summary>
/// <param name="a">Parameter A</param>
new(a:int) = MyType(a, ())
I added a dummy parameter u to the implicit constructor, so that it can be called from the public constructor. Anyway, I think this should be considered as a language bug and so I'd suggest reporting this to fsbugs at microsoft dot com.
As an aside, I think the XML documentation is mainly useful as a source of data for IntelliSense (which still needs documentation for the constructor, though) and I created some alternative F# tools that let you create tutorials and documentation by writing an F# script file with special comments using Markdown (there is a blog post about it) - so you may consider that as a useful addition to the standard XML tooling.

In exactly the same way as you do in C#: http://msdn.microsoft.com/en-us/library/dd233217.aspx
If you don't put any tags, F# assumes it is "summary":
/// This is the documentation
type MyType() = ....
... is equivalent to
/// <summary>This is the documentation</summary>
type MyType() = ...
If you want to document a constructor, you'll have to declare it explicitely. AFAIK there is no way to document the primary constructor.
/// [Type summary goes here]
type MyType(a : int) =
let m_a = a
/// [Parameterless constructor documentation here]
new() = MyType(0)

There is no way to document the implicit constructor with an XML comment inside an F# source file (.fs). One workaround is to declare the constructor explicitly (see Dr Asik's answer). Another is to put your XML comments into an F# Signature File (.fsi).
File.fs:
module File
type Awesome(sauce: string) =
member x.Sauce = sauce
File.fsi
module File
type Awesome =
class
/// Implicit constructor summary for the Awesome type
new : sauce:string -> Awesome
member Sauce : string
end
The XML documentation for this assembly will now contain the correct summary:
<member name="M:File.Awesome.#ctor(System.String)">
<summary>
Implicit constructor summary for the Awesome type
</summary>
</member>

This really is an annoying problem.
Another solution I ended up using is to not rely on a primary constructor:
/// Documentation type.
type Awesome =
val sauce : string
/// <summary>Documentation constructor.</summary>
/// <param name="sauce">Sauce. Lots of it.</param>
new (sauce) = { sauce = sauce }
More verbose, but no extra files or private constructors needed...

Related

CS1580 fired with a paramater that is generic and nullable (xml doc)

I'm using VS2019 16.1.1 (in VS2017 15.9.12 this does not happen)
I have method with this definition:
public static bool HasValue<T>(this T? source) where T : struct
and in a xml doc I have this:
/// <summary>
/// Internally uses <see cref="TypeValidations.HasValue{T}(T?)"/>.
/// </summary>
In VS2019 this fires the error:
Error CS1580 Invalid type for parameter T? in XML comment cref attribute: 'TypeValidations.HasValue{T}(T?)'
If I change the documentation to TypeValidations.HasValue{T}(Nullable{T}) now I got an analyzer error (SA1125) telling me that I should use the shorthand notation for nullable types.
Is there a "standard" way to refer to nullable types in the XML documentation that not generate an error in the analyzers or the compiler?
Edit:
repo code

NancyFx F# App with parameter

I am trying to get this example translated from C# to F#
public class MyModule : NancyModule
{
private IMyDependency _dependency;
public MyModule(IMyDependency dependency)
{
_dependency = dependency;
Get["/"] = x =>
{
};
// Register other routes
}
}
(source 1)
However adding a parameter to constructor
type HelloModule(dependency) as self =
inherit NancyModule()
do
self.Get.["/"] <- fun _ -> "Hello" :> obj
(source 2)
results in a run-time exception: System.InvalidOperationException: 'Something went wrong when trying to satisfy one of the dependencies during composition ...
How can I correctly add a dependency like a data-source to the code? Or, generally, how do I pass something from outside of HelloModule to the inside?
I'm guessing this might be caused by not specifying the type of the dependency parameter of the constructor in your F# code. This would result in the F# compiler assigning that parameter a generic type, and then Nancy's dependency injection framework doesn't know what to inject.
Try the following and see if it fixes your problem:
type HelloModule(dependency : IMyDependency) as self =
inherit NancyModule()
do
self.Get.["/"] <- fun _ -> "Hello" :> obj
P.S. Naturally, for this to work, you'll also need to have some type that implements the IMyDependency interface, and have told the Nancy framework about that type. From this part of the Nancy documentation that you linked to, it looks like merely declaring the type is enough, but if that's not actually enough then you'll have to register the type manually. I'm not familiar enough with Nancy to give you specific advice there beyond what the documentation says.

How to convert C# code that uses Shell COM to F#?

I have the following C# method:
private static bool IsLink(string shortcutFilename)
{
var pathOnly = Path.GetDirectoryName(shortcutFilename);
var filenameOnly = Path.GetFileName(shortcutFilename);
var shell = new Shell32.Shell();
var folder = shell.NameSpace(pathOnly);
var folderItem = folder.ParseName(filenameOnly);
return folderItem != null && folderItem.IsLink;
}
I have tried converting this to F# as:
let private isLink filename =
let pathOnly = Path.GetDirectoryName(filename)
let filenameOnly = Path.GetFileName(filename)
let shell = new Shell32.Shell()
let folder = shell.NameSpace(pathOnly)
let folderItem = folder.ParseName(filenameOnly)
folderItem <> null && folderItem.IsLink
It however reports an error for the let shell = new Shell32.Shell() line, saying that new cannot be used on interface types.
Have I just made a silly syntactic mistake, or is there extra work needed to access COM from F#?
I don't know enough about the F# compiler but your comments makes it obvious enough. The C# and VB.NET compilers have a fair amount of explicit support for COM built-in. Note that your statement uses the new operator on an interface type, Shell32.Shell in the interop library looks like this:
[ComImport]
[Guid("286E6F1B-7113-4355-9562-96B7E9D64C54")]
[CoClass(typeof(ShellClass))]
public interface Shell : IShellDispatch6 {}
IShellDispatch6 is the real interface type, you can also see the IShellDispatch through IShellDispatch5 interfaces. That's versioning across the past 20 years at work, COM interface definitions are immutable since changing them almost always causes an undiagnosable hard crash at runtime.
The [CoClass] attribute is the important one for this story, that's what the C# compiler goes looking for you use new on a [ComImport] interface type. Tells it to create the object by creating an instance of Shell32.ShellClass instance and obtain the Shell interface. What the F# compiler doesn't do.
ShellClass is a fake class, it is auto-generated by the type library importer. COM never exposes concrete classes, it uses a hyper-pure interface-based programming paradigm. Objects are always created by an object factory, CoCreateInstance() is the workhorse for that. Itself a convenience function, the real work is done by the universal IClassFactory interface, hyper-pure style. Every COM coclass implements its CreateInstance() method.
The type library importer makes ShellClass look like this:
[ComImport]
[TypeLibType(TypeLibTypeFlags.FCanCreate)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("13709620-C279-11CE-A49E-444553540000")]
public class ShellClass : IShellDispatch6, Shell {
// Methods
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(0x60040000)]
public virtual extern void AddToRecent([In, MarshalAs(UnmanagedType.Struct)] object varFile, [In, Optional, MarshalAs(UnmanagedType.BStr)] string bstrCategory);
// Etc, many more methods...
}
Lots of fire and movement, none of it should ever be used. The only thing that really matters is the [Guid] attribute, that provides the CLSID that CoCreateInstance() needs. It also needs the IID, the [Guid] of the interface, provided by the interface declaration.
So the workaround in F# is to create the Shell32.ShellClass object, just like the C# compiler does implicitly. While technically you can keep the reference in a ShellClass variable, you should strongly favor the interface type instead. The COM way, the pure way, it avoids this kind of problem. Ultimately it is the CLR that gets the job done, it recognizes the [ClassInterface] attribute on the ShellClass class declaration in its new operator implementation. The more explicit way in .NET is to use Type.GetTypeFromCLSID() and Activator.CreateInstance(), handy when you only have the Guid of the coclass.

F# Val without Self Identifier

Just curious why F# has:
member val Foo = ... with get, set
While omitting the self identifier (e.g. this.).
This is still an instance property. Maybe I am the only one confused when using it. But just bothered me enough to query whoever knows how the language was defined.
With this syntax, the property is almost totally auto-implemented -- all you provide is the initialization code, which essentially runs as part of the constructor.
One of the best-practice guard rails F# puts in place is that it does not let you access instance members before the instance is fully initialized. (wow, crazy idea, right?).
So you would have no use for a self-identifier in auto-props, anyways, since the only code you get to write is init code that can't touch instance members.
Per the MSDN docs (emphasis mine):
Automatically implemented properties are part of the initialization of
a type, so they must be included before any other member definitions,
just like let bindings and do bindings in a type definition. Note that
the expression that initializes an automatically implemented property
is only evaluated upon initialization, and not every time the property
is accessed. This behavior is in contrast to the behavior of an
explicitly implemented property. What this effectively means is that
the code to initialize these properties is added to the constructor of
a class.
Btw, if you try to be a smartass and use the class-level self-identifier to get around this, you'll still blow up at runtime:
type A() as this =
member val X =
this.Y + 10
with get, set
member this.Y = 42
let a = A()
System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized.
at Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicFunctions.FailInit()
at FSI_0013.A.get_Y()
at FSI_0013.A..ctor()
at <StartupCode$FSI_0014>.$FSI_0014.main#()
Edit: Worth noting that in upcoming C# 6, they also now allow auto-props with initializers (more F# features stolen for C#, shocker :-P), and there is a similar restriction that you can't use the self-identifier:
class A
{
// error CS0027: Keyword 'this' is not available in the current context
public int X { get; set; } = this.Y + 10;
public int Y = 42;
public A() { }
}

Biztalk mapping Date to String

I'm working on a biztalk project and use a map to create the new message.
Now i want to map a datefield to a string.
I thought i can do it on this way with an Function Script with inline C#
public string convertDateTime(DateTime param)
{
return System.Xml.XmlConvert.ToString(param,ÿyyyMMdd");
}
But this doesn't work and i receive an error. How can i do the convert in the map?
It's a Biztalk 2006 project.
Without the details of the error you are seeing it is hard to be sure but I'm quite sure that your map is failing because all the parameters within the BizTalk XSLT engine are passed as strings1.
When I try to run something like the function you provided as inline C# I get the following error:
Object of type 'System.String' cannot be converted to type 'System.DateTime'
Replace your inline C# with something like the following:
public string ConvertDateTime(string param1)
{
DateTime inputDate = DateTime.Parse(param1);
return inputDate.ToString("yyyyMMdd");
}
Note that the parameter type is now string, and you can then convert that to a DateTime and perform your string format.
As other answers have suggested, it may be better to put this helper method into an external class - that way you can get your code under test to deal with edge cases, and you also get some reuse.
1 The fact that all parameters in the BizTalk XSLT are strings can be the source of a lot of gotchas - one other common one is with math calculations. If you return numeric values from your scripting functoids BizTalk will helpfully convert them to strings to map them to the outbound schema but will not so helpfully perform some very random rounding on the resulting values. Converting the return values to strings yourself within the C# will remove this risk and give you the expected results.
If you're using the mapper, you just need a Scripting Functiod (yes, using inline C#) and you should be able to do:
public string convertDateTime(DateTime param)
{
return(param.ToString("YYYYMMdd");
}
As far as I know, you don't need to call the System.Xml namespace in anyway.
I'd suggest
public static string DateToString(DateTime dateValue)
{
return String.Format("{0:yyyyMMdd}", dateValue);
}
You could also create a external Lib which would provide more flexibility and reusability:
public static string DateToString(DateTime dateValue, string formatPicture)
{
string format = formatPicture;
if (IsNullOrEmptyString(formatPicture)
{
format = "{0:yyyyMMdd}";
}
return String.Format(format, dateValue);
}
public static string DateToString(DateTime dateValue)
{
return DateToString(dateValue, null);
}
I tend to move every function I use twice inside an inline script into an external lib. Iit will give you well tested code for all edge cases your data may provide because it's eays to create tests for these external lib functions whereas it's hard to do good testing on inline scripts in maps.
This blog will solve your problem.
http://biztalkorchestration.blogspot.in/2014/07/convert-datetime-format-to-string-in.html?view=sidebar
Regards,
AboorvaRaja
Bangalore
+918123339872
Given that maps in BizTalk are implemented as XSL stylesheets, when passing data into a msxsl scripting function, note that the data will be one of types in the Equivalent .NET Framework Class (Types) from this table here. You'll note that System.DateTime isn't on the list.
For parsing of xs:dateTimes, I've generally obtained the /text() node and then parse the parameter from System.String:
<CreateDate>
<xsl:value-of select="userCSharp:GetDateyyyyMMdd(string(s0:StatusIdChangeDate/text()))" />
</CreateDate>
And then the C# script
<msxsl:script language="C#" implements-prefix="userCSharp">
<![CDATA[
public System.String GetDateyyyyMMdd(System.String p_DateTime)
{
return System.DateTime.Parse(p_DateTime).ToString("yyyyMMdd");
}
]]>

Resources