Skip to content

Instantly share code, notes, and snippets.

@mausch
Last active January 4, 2020 10:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mausch/5178055 to your computer and use it in GitHub Desktop.
Save mausch/5178055 to your computer and use it in GitHub Desktop.
#r @"bin\debug\FsControl.Core.dll" // from https://github.com/gmpl/FsControl
open System
open FsControl.Core.Abstractions
// generic semigroup operator
let inline mappend x y = Inline.instance (Monoid.Mappend, x) y
// generic functor operators
let inline fmap f x = Inline.instance (Functor.Fmap, x) f
let inline (<!>) a b = fmap a b
// generic applicative functor operators
let inline puree x = Inline.instance Applicative.Pure x
let inline ap y x = Inline.instance (Applicative.Ap, x, y) ()
let inline (<*>) a b = ap b a
// generic monad operator
let inline (>>=) x f = Inline.instance (Monad.Bind, x) f
// Validation definition
type Validation<'a,'e> = Success of 'a | Failure of 'e
with
// Validation is an instance of Applicative
static member inline instance (Applicative.Pure, _:Validation<_,_>) = Success
static member inline instance (Applicative.Ap, f: Validation<_, _>, x: Validation<_, _>, _) =
fun () ->
match f,x with
| Success f , Success x -> Success (f x)
| Failure e , Success x -> Failure e
| Success f , Failure e -> Failure e
| Failure e1, Failure e2 -> Failure (mappend e1 e2) // works with anything that has mappend. Can be a semigroup, not necessarily a monoid
// Validation is an instance of Functor
// Derived from applicative
static member inline instance (Functor.Fmap, v: Validation<_,_>, _) =
fun f -> Functor.DefaultImpl.FmapFromApplicative f v
// Sadly, DateTime.TryCreate is internal in .NET
module DateTime =
let tryCreate y m d =
try
Some (DateTime(y,m,d))
with _ -> None
// Some validation functions
module Validators =
let inline toChoice x =
match x with
| Success a -> Choice1Of2 a
| Failure e -> Choice2Of2 e
let inline fromChoice x =
match x with
| Choice1Of2 a -> Success a
| Choice2Of2 e -> Failure e
let inline integer a =
match Int32.TryParse a with
| true, a -> Success a
| _ -> Failure (puree (sprintf "Invalid integer %s" a))
let inline date y m d : Validation<DateTime, _> =
DateTime.tryCreate <!> integer y <*> integer m <*> integer d
|> toChoice // convert to Choice, since Validation is not a monad
>>= (function Some x -> Choice1Of2 x | _ -> Choice2Of2 (puree "Invalid date"))
|> fromChoice
// trying the validation
let parsedDate : Validation<DateTime, string list> =
Validators.date "1999" "2" "30" // Failure ["Invalid date"]
// A difference list ( http://en.wikipedia.org/wiki/Difference_list )
type 'a DList = DList of ('a list -> 'a list)
with
// DList is a monoid
static member inline instance (Monoid.Mempty, _: 'a DList) =
fun () -> DList id
static member inline instance (Monoid.Mappend, DList x, _) =
fun (DList y) -> DList (y >> x)
// DList applicative
static member inline instance (Applicative.Pure, _: 'a DList) =
fun x -> DList (fun r -> x::r)
// TODO ap, I'm too lazy and it's not needed for this example
// Same code with a different error accumulator
let parsedDateDList : Validation<DateTime, string DList> =
Validators.date "1999" "2" "30"
// A non-empty list
type 'a NonEmptyList = { Head: 'a; Tail: 'a list }
with
// non-empty list is a semigroup
static member inline instance (Monoid.Mappend, x: _ NonEmptyList, _) =
fun (y: _ NonEmptyList) ->
{ Head = x.Head
Tail = x.Tail @ (y.Head::y.Tail) }
// non-empty list is applicative
static member inline instance (Applicative.Pure, _: _ NonEmptyList) =
fun x -> { Head = x; Tail = [] }
// TODO ap, I'm too lazy and it's not needed for this example
// Same code with a different error accumulator
let parsedDateNEL : Validation<DateTime, string NonEmptyList> =
Validators.date "1999" "2" "30"
@t0yv0
Copy link

t0yv0 commented Oct 27, 2013

This is quite cool! Perhaps the most concise way to write higher-kinded code I've seen.. This should be on FsControl home page, will make a good case..

Have you used it more in code? I'm assuming the technique breaks down at some point? Generic monad transformers probably won't make it?

It is a pity F# language does not give us more direct support for HK. Wonder if F* has something to offer here.

@mausch
Copy link
Author

mausch commented Oct 28, 2013

@ToyVo I have a blogpost draft including this example since April or so, comparing this with the equivalent Haskell code. My explanations suck so far so I can't publish it yet.

I haven't used this in anger yet. I write C# and VB.NET for my day job, so I don't get to write a lot of F#. I tried a previous incarnation of FsControl, over a year ago, and found some issues with type inference . I haven't tried that code with a recent FsControl. Also, since there aren't any real higher kinds, the signatures don't say much. Pervasive inlining tends to bloat the code (not sure how much), and there may be performance issues (again don't know how much).
Not surprisingly, there are some issues with subtyping, e.g. Functor is defined for list but not for seq. I think if you define it for seq you get some ambigüity in the dispatch for list.

There are some monad transformers in FsControl I haven't tried them yet.

So there are many tradeoffs to this, but the benefits are pretty clear, and so I think this should be given a real chance before shooting it down as "unnatural" or such. All the tradeoffs should be discovered and documented, so that a real decision can be made about whether to use it or not in some situation. I talked to Gustavo a while ago and he mentioned he was using it for some generic numeric code. Send him an email and ask him :)

IIRC F* does support higher kinds. I tried some simple code some time ago (can't remember what it was) and the compiler consumed 4.5GB memory and hanged (not even crashed) :-/

@t0yv0
Copy link

t0yv0 commented Oct 28, 2013

Hehe, fun stuff with F*. On a similar note, I played a bit with extraction from Coq to OCaml, looks like the code is mostly compilable with F# so there's another (inconvenient) route for running "type-intensive" code on .NET.

I'm with you on FsControl. It's a brilliant solution with some serious trade-offs. Good to know there's monad transformers, looks like it works by underconstraining bind ('ma and 'mb for 'm 'a and 'm 'b).

@gusty
Copy link

gusty commented Nov 16, 2013

I just forked and updated the gist to make it work with the latest version of FsControl.

@wallymathieu
Copy link

wallymathieu commented Jan 6, 2018

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