Skip to content

Instantly share code, notes, and snippets.

@panesofglass
Last active August 29, 2015 14:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save panesofglass/8b8ac1db9dc89e190bb9 to your computer and use it in GitHub Desktop.
Save panesofglass/8b8ac1db9dc89e190bb9 to your computer and use it in GitHub Desktop.
Experimenting with a potential solution for implementing protocols in F#
#nowarn "0686"
[<AbstractClass>]
type ListLike<'T>() =
abstract Head : 'T with get
abstract Tail : ListLike<'T> with get
abstract Length : int
static member op_Implicit(list: 'T list) =
{ new ListLike<'T>() with
member __.Head = List.head list
member __.Tail = ListLike.op_Implicit(List.tail list)
member __.Length = List.length list }
static member op_Implicit(seq: 'T seq) =
{ new ListLike<'T>() with
member __.Head = Seq.head seq
member __.Tail = ListLike.op_Implicit(seq |> Seq.skip 1 |> Seq.cache)
member __.Length = Seq.length seq }
static member op_Implicit(array: 'T[]) =
{ new ListLike<'T>() with
member __.Head = array.[0]
member __.Tail = ListLike.op_Implicit(array.[1..])
member __.Length = array.Length }
static member op_Implicit(option: 'T option) =
{ new ListLike<'T>() with
member __.Head = match option with | Some x -> x | None -> failwith "No value"
member __.Tail = match option with | Some x -> ListLike.op_Implicit None | None -> failwith "No value"
member __.Length = match option with | Some x -> 1 | None -> 0 }
module ListLike =
let head (x: ListLike<_>) = x.Head
let tail (x: ListLike<_>) = x.Tail
let length (x: ListLike<_>) = x.Length
ListLike.head [1;2;3;4] |> printfn "%i"
#nowarn "0686"
[<AbstractClass>]
type ListLike<'T>() =
abstract Head : 'T with get
abstract Tail : ListLike<'T> with get
abstract Length : int
static member op_Implicit(list: 'T list) =
{ new ListLike<'T>() with
member __.Head = List.head list
member __.Tail = ListLike.op_Implicit(List.tail list)
member __.Length = List.length list }
static member op_Implicit(seq: 'T seq) =
{ new ListLike<'T>() with
member __.Head = Seq.head seq
member __.Tail = ListLike.op_Implicit(seq |> Seq.skip 1 |> Seq.cache)
member __.Length = Seq.length seq }
static member op_Implicit(array: 'T[]) =
{ new ListLike<'T>() with
member __.Head = array.[0]
member __.Tail = ListLike.op_Implicit(array.[1..])
member __.Length = array.Length }
static member op_Implicit(option: 'T option) =
{ new ListLike<'T>() with
member __.Head = match option with | Some x -> x | None -> failwith "No value"
member __.Tail = match option with | Some x -> ListLike.op_Implicit None | None -> failwith "No value"
member __.Length = match option with | Some x -> 1 | None -> 0 }
module ListLike =
let inline cast x = (^a : (static member op_Implicit : 'b -> ^a) x)
let head x =
let interim = cast<_, ListLike<_>> x
interim.Head
ListLike.head [1;2;3;4] |> printfn "%i"
#nowarn "0686"
let inline cast x = (^a: (static member Cast : 'b -> ^a) x)
[<AbstractClass>]
type ListLike<'T>() =
abstract Head : 'T with get
abstract Tail : ListLike<'T> with get
abstract Length : int
// Ideal would be to use op_Implicit rather than Cast, but op_Implicit can only be added on the original type definition.
static member Cast(list: 'T list) =
{ new ListLike<'T>() with
member __.Head = List.head list
member __.Tail = ListLike.Cast(List.tail list)
member __.Length = List.length list }
static member Cast(seq: 'T seq) =
{ new ListLike<'T>() with
member __.Head = Seq.head seq
member __.Tail = ListLike.Cast(seq |> Seq.skip 1 |> Seq.cache)
member __.Length = Seq.length seq }
static member Cast(array: 'T[]) =
{ new ListLike<'T>() with
member __.Head = array.[0]
member __.Tail = ListLike.Cast(array.[1..])
member __.Length = array.Length }
module Option =
type ListLike<'T> with
static member Cast(option: 'T option) =
{ new ListLike<'T>() with
member __.Head = match option with | Some x -> x | None -> failwith "No value"
member __.Tail = match option with | Some x -> ListLike.Cast None | None -> failwith "No value"
member __.Length = match option with | Some x -> 1 | None -> 0 }
let listCast = cast<_, ListLike<_>> [1;2;3;4]
listCast.Head |> printfn "%i"
listCast.Tail.Tail.Head |> printfn "%i"
let arrayCast = cast<_, ListLike<_>> [|1;2;3;4|]
arrayCast.Head |> printfn "%i"
listCast.Tail.Tail.Head |> printfn "%i"
let seqCast = seq { yield 1; yield 2; yield 3; yield 4 } |> cast<_, ListLike<_>>
seqCast.Head |> printfn "%i"
listCast.Tail.Tail.Head |> printfn "%i"
let optCast = Some 1 |> cast<_, ListLike<_>>
seqCast.Head |> printfn "%i"
listCast.Tail.Tail.Head |> ignore // will throw
@panesofglass
Copy link
Author

Sadly, statically resolved type parameters only appear to work on members defined on the type and do not pick up extension members. Therefore, the cast in line 53 will fail to compile. This is effectively the same issue as not allowing op_Implicit to be defined outside the type definition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment