Let's say I have a significant class hierarchy:
Tag
ControlFlowTag
IfTag
ForTag
JumpTag
HTMLTag
DivTag
and I want to make a list interspersed with these and strings.
let MyList = [tagA, tagB, "some text", tagC]
and I thought I could discriminated union it
type Node =
| Tag of Tag
| String of String
let MyList: list<Node> = [tagA, tagB, "some text", tagC]
but alas, it doesn't work without
let MyList: list<Node> = [Tag tagA, Tag tagB, String "some text", Tag tagC]
Obviously the Tag and String described in Node are orthogonal and separate from the existing Tag/String classes. Mousing over gives me the types as Node.Tag and Node.String, which isn't what I want.
What I have now is a function t which creates a StringTag which inherits from Tag, giving me
let MyList : list<Tag> = [tagA, tagB, t"some text", tagC]
which is pretty nice, but the extra t adds to the visual noise. What I actually want is a strongly typed "list of two different types" which I could work with using match statements. I thought that was the point of Discriminated Unions, but their inability to use existing type hierarchies is a problem, since the existing hierarchy (in this case Tag) is complex enough I think a full OO-inheritence approach to that subset of types is clearer than a pure Discriminated Union approach
One option is to just make it a list of obj and cast everything before/during the match, but that's not really very nice. Are there any other approaches?
If you had two different DUs, say
type Node =
| Tag of Tag
| String of String
and
type Foo =
| Bar of Tag
| Name of String
how would the compiler know of which type the following list is?
[tagA; tagB; "some text"; tagC]
As svick said, the discriminator is necessary. If you use classes instead you'll need to upcast to the base type, so I'm not sure you save on keystrokes.
If you're working with dictionaries, here is a nice option to reduce the syntactic noise of boxing. Maybe you can do something similar for lists.
I don't know how helpful this is, but you can use Active Patterns to match a class hierarchy in a DU-like fashion if appropriate.
[<AbstractClass>]
type Animal() =
abstract Talk : string
type Cat() =
inherit Animal()
override this.Talk = "Meow"
type Dog() =
inherit Animal()
override this.Talk = "Woof"
type SuperCat(s) =
inherit Cat()
override this.Talk = s
let animals : list<Animal> =
[Dog(); Cat(); SuperCat("MEOW")]
let (|SCSaid|_|) (a:Animal) = // Active Pattern
match a with
| :? SuperCat as sc -> Some sc.Talk
| _ -> None
for a in animals do
match a with
| :? Dog -> printfn "dog"
| SCSaid s -> printfn "SuperCat said %s" s // looks like DU
| _ -> printfn "other"
//dog
//other
//SuperCat said MEOW
Discriminated unions are just that – discriminated (unlike e.g. C unions). That means you have to always add the discriminator.
If this were C#, I would think about having an implicit conversion from string to StringTag. But since F# doesn't support implicit conversions, I think the second approach is your best bet. Although I would make the function's name more descriptive, not just t. Most of the time, it's better to write code that's easy to read, not code that's easy to write.
Related
I have found this superb JSON library for F#, it's inspired by Elm's Json.Decode and it defines a basic Decoder type like this:
type Decoder<'T> = string -> obj -> Result<'T, DecoderError> (here)
There are functions like Decode.map and I wish I could make it F#+ compatible, so I could use it like:
let customerId = Decode.string |>> CustomerId
(see |>> infix version of generic map)
As far as I can see, to make a 3rd party library use F#+ concepts like Functors, one needs to extend the 3rd party type with static member Map, but the Decoder<'T> is just an abbreviation.
Is there any solution? Did anyone try?
P.S.
My solution so far is a custom binding:
let (<!>) = Decode.map
let customerId: Decoder<CustomerId> = CustomerId <!> Decode.string
let submitId: Decoder<SubmitId> = SubmitId <!> Decode.string
let currency: Decoder<Currency> = Currency <!> Decode.string
// etc…
The problem is that you don't control the code. If you had control over it, a solution would be:
Implement Decoder as a concrete type as opposed to a type alias. A type alias can't have additional members, because it's not really another type. Library authors should use single case discriminated unions, now that you can make them structs with nearly zero overhead.
Add the member Map with the required signature.
Alternatively, if extensions members becomes visible for trait constraints in a future F# version, you would be able just extend the type with the Map function, maybe with some undesired effects as it's a type alias for a function with 2 arguments.
So, I think the best you can do is what you already showed.
As a side note, you might be interested in having a look at Fleece which also provides decoders, but it also goes one step further and provides you codecs which goes in both directions (and you can map and <*> over it).
Tried searching everywhere.
Looking at https://github.com/fsharp/FAKE/blob/master/src/app/Fake.IIS/IISHelper.fs#L64 with parameter string * string.
Tried to instantiate in F# code and received error FS0010: Incomp
lete structured construct at or before this point in expression.
What in the world is this and how does on instantiate this?
string*string is a pair of two strings and is roughly equal to Tuple<string, string>. string*int*decimal is then roughly equal to Tuple<string, int, decimal>.
You create an instance of string*string using the following expression "first", "second". An instance of string*int*decimal is created like so "1", 2, 3.0M.
For more information on tuples see: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/tuples
The rationale for this notation is easier to see if you consider that F# has an algebra for creating types; * create tuples and | creates unions.
// Tree is roughly equal to having an interface Tree
// with three implementations Empty, Leaf and Fork
type Tree<'T> =
| Empty
| Leaf of 'T
| Fork of Tree<'T>*Tree<'T>
Having an algebra for type creation is very powerful as Scott Wlaschin demonstrates: https://fsharpforfunandprofit.com/ddd/
I love the simplicity of types like
type Code = Code of string
But I would like to put some restrictions on string (in this case - do not allow empty of spaces-only strings). Something like
type nonemptystring = ???
type Code = Code of nonemptystring
How do I define this type in F# idiomatic way? I know I can make it a class with constructor or a restricted module with factory function, but is there an easy way?
A string is essentially a sequence of char values (in Haskell, BTW, String is a type alias for [Char]). A more general question, then, would be if it's possible to statically declare a list as having a given size.
Such a language feature is know as Dependent Types, and F# doesn't have it. The short answer, therefore, is that this is not possible to do in a declarative fashion.
The easiest, and probably also most idiomatic, way, then, would be to define Code as a single-case Discriminated Union:
type Code = Code of string
In the module that defines Code, you'd also define a function that clients can use to create Code values:
let tryCreateCode candidate =
if System.String.IsNullOrWhiteSpace candidate
then None
else Some (Code candidate)
This function contains the run-time logic that prevents clients from creating empty Code values:
> tryCreateCode "foo";;
val it : Code option = Some (Code "foo")
> tryCreateCode "";;
val it : Code option = None
> tryCreateCode " ";;
val it : Code option = None
What prevents a client from creating an invalid Code value, then? For example, wouldn't a client be able to circumvent the tryCreateCode function and simply write Code ""?
This is where signature files come in. You create a signature file (.fsi), and in that declare types and functions like this:
type Code
val tryCreateCode : string -> Code option
Here, the Code type is declared, but its 'constructor' isn't. This means that you can't directly create values of this types. This, for example, doesn't compile:
Code ""
The error given is:
error FS0039: The value, constructor, namespace or type 'Code' is not defined
The only way to create a Code value is to use the tryCreateCode function.
As given here, you can no longer access the underlying string value of Code, unless you also provide a function for that:
let toString (Code x) = x
and declare it in the same .fsi file as above:
val toString : Code -> string
That may look like a lot of work, but is really only six lines of code, and three lines of type declaration (in the .fsi file).
Unfortunately there isn't convenient syntax for declaring a restricted subset of types but I would leverage active patterns to do this. As you rightly say, you can make a type and check it's validity when you construct it:
/// String type which can't be null or whitespace
type FullString (string) =
let string =
match (System.String.IsNullOrWhiteSpace string) with
|true -> invalidArg "string" "string cannot be null or whitespace"
|false -> string
member this.String = string
Now, constructing this type naively may throw runtime exceptions and we don't want that! So let's use active patterns:
let (|FullStr|WhitespaceStr|NullStr|) (str : string) =
match str with
|null -> NullStr
|str when System.String.IsNullOrWhiteSpace str -> WhitespaceStr
|str -> FullStr(FullString(str))
Now we have something that we can use with pattern matching syntax to build our FullStrings. This function is safe at runtime because we only create a FullString if we're in the valid case.
You can use it like this:
let printString str =
match str with
|NullStr -> printfn "The string is null"
|WhitespaceStr -> printfn "The string is whitespace"
|FullStr fstr -> printfn "The string is %s" (fstr.String)
This compiler like:
let test Xf Yf = Xf + Yf
This compiler no like:
let test Xfd Yfd = Xfd + Yfd
Warning:
Uppercase variable identifiers should not generally be used in patterns, and may indicate a misspelt pattern name.
Maybe I'm not googling properly, but I haven't managed to track down anything which explains why this is the case for function parameters...
I agree that this error message looks a bit mysterious, but there is a good motivation for it. According to the F# naming guidelines, cases of discriminated unions should be named using PascalCase and the compiler is trying to make sure that you don't accidentally misspell name of a case in pattern matching.
For example, if you have the following union:
type Side =
| Left
| Right
You could write the following function that prints "ok" when the argument is Left and "wrong!" otherwise:
let foo a =
match a with
| Lef -> printfn "ok"
| _ -> printfn "wrong!"
There is a typo in the code - I wrote just Lef - but the code is still valid, because Lef can be interpreted as a new variable and so the matching assigns whatever side to Lef and always runs the first case. The warning about uppercase identifiers helps to avoid this.
F# tries to enforce case rules for active patterns - consider what does this code do
let f X =
match X with
|X -> 1
|_ -> 2
This is quite confusing. Also, function parameters are similar to patterns, you can do
let f (a,b,_) = a,b
for example. Not quite sure why the third letter triggers the warning though
I've been googling for a while now... Ok, I'm sorry, this one is pathetically easy but is there an operator in F# to compare class types, like the 'is' keyword in C#? I don't want to use a full blown match statement or start casting things. Cheers
You can use the :? construct both as a pattern (inside match) or as an operator:
let foo = bar :? System.Random
This behaves slightly differently than in C#, because the compiler still tries to do some checks at compile-time. For example, it is an error to use this if the result would be surely false:
let bar = 42
let foo = bar :? System.Random // Error
I don't think this could lead to confusion, but you can always add box to convert the argument to obj, which can be tested against any type:
let foo = box bar :? System.Random
If you want a general C#-to-F# quick-reference, see
http://lorgonblog.wordpress.com/2008/11/28/what-does-this-c-code-look-like-in-f-part-one-expressions-and-statements/
which answers this question and many others.