Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define the cons (::) operator for custom collections

Tags:

f#

I am using the fairly popular FSharpx.Collections package, and in particular the NonEmptyList type.

This type provides the NonEmptyList.cons function, but I want to use the :: operator as with regular List, i.e. head :: tail. Since tail must already be a NonEmptyList<'a>, there shouldn't be any conflict with List's ::operator.

However, it seems I cannot define the operator. This:

let ( :: ) h t = NonEmptyList.cons h t

results in a compilation error:

Unexpected symbol '::' in pattern. Expected ')' or other token.

I know that :: is not quite in the same category as other operators, but I don't fully understand how. So I tried a few things more or less at random, such as replacing :: with op_cons and the like, without success.

Am I missing something, and is there a way to do what I want to do?

like image 380
piaste Avatar asked Aug 30 '15 14:08

piaste


1 Answers

According to MSDN, colon cannot actually be used in operator name. This seems to contradict the F# specification from FSharp.org, I'm not sure what's going on there. But we can verify that in FSI:

> let ( >:> ) a b = a+b
Script.fsx(1,7): error FS0035: This construct is deprecated: ':' is not permitted as a character in operator names and is reserved for future use

If you look at how List<'T> is defined, you'll find that (::) is not actually an operator, but a case constructor:

type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list

And sure enough, you can define your own DU type with that as constructor name:

> type A = 
>   | ( :: ) of string * int
>   | A of int
> 
> let a = "abc" :: 5

val a : A = Cons ("abc",5)

Now, oddly, if I try to use another operator-ish-looking name as case constructor, I get this error:

> type A = | ( |> ) of string * int
Script.fsx(1,14): error FS0053: Discriminated union cases and exception labels must be uppercase identifiers

Which means that (::) is somehow special (and so is ([]), by the way).

So the bottom line seems to be - no, you can't do that.
But why do you even need to? Can you, perhaps, settle for a more acceptable operator name, which would still express the semantics of "cons" - like, say, (<+>)?

like image 193
Fyodor Soikin Avatar answered Sep 22 '22 13:09

Fyodor Soikin