Skip to content

Instantly share code, notes, and snippets.

@alexr
Created October 25, 2014 22:13
Show Gist options
  • Save alexr/3c76de68684449cd3d6d to your computer and use it in GitHub Desktop.
Save alexr/3c76de68684449cd3d6d to your computer and use it in GitHub Desktop.
This is an F# implementation of the Omega monad as it appears, more or less, on http://hackage.haskell.org/package/control-monad-omega. Type annotations are for reading convenience.
(**
And of cause translation of the context free grammar enumeration example
from [Luke Palmer's post](http://lukepalmer.wordpress.com/2008/05/02/enumerating-a-context-free-language/)
which was my original inpiration to use Omega.
Notice the explicit addition of laziness to allow recursive use of
`symbol` literals in a list. Alternatively one can define, say:
```
type 'a symbol =
| Terminal of 'a
| Nonterminal of 'a symbol stream stream
```
But then defining the grammar would be much more verbose.
*)
open Omega
open OmegaBuilder
type 'a symbol =
| Terminal of 'a
| Nonterminal of (unit -> 'a symbol list list)
(**
To define enumerate we need to borrow a couple of more functions from Haskell...
*)
(**
-- http://hackage.haskell.org/package/base-4.7.0.1/docs/src/Control-Monad.html#sequence
sequence :: Monad m => [m a] -> m [a]
sequence ms = foldr k (return []) ms
where
k m m' = do { x <- m; xs <- m'; return (x:xs) }
*)
let sequence ms =
let k m m' = omega { for x in m do for xs in m' do yield x::xs }
List.foldBack k ms (single [])
(**
-- http://hackage.haskell.org/package/base-4.7.0.1/docs/src/Control-Monad.html#mapM
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f xs = sequence (map f xs)
*)
let mapM f xs = sequence (List.map f xs)
(* enumerate: 'a Symbol -> 'a list stream *)
let rec enumerate = function
| Terminal a -> single [a]
| Nonterminal falts ->
omega {
let alts = falts() //-- to unwrap explicitly added laziness
for alt in each alts do //-- for each alternative
//-- (each is the Omega constructor :: [a] -> Omega a)
for rep in mapM enumerate alt do //-- enumerate each symbol in the sequence
yield List.concat rep //-- and concatenate the results
}
let rec arithGrammar
= Nonterminal (fun () -> [ [add] ])
and add = Nonterminal (fun () -> [ [mul]; [add; Terminal "+"; mul] ])
and mul = Nonterminal (fun () -> [ [term]; [mul; Terminal "*"; term] ])
and term = Nonterminal (fun () -> [ [number]; [Terminal "("; arithGrammar; Terminal ")"] ])
and number = Nonterminal (fun () -> [ [digit]; [digit; number] ])
and digit = Nonterminal (fun () -> List.map (fun x -> [x]) <| (List.map (Terminal << string)) [0..9] )
// > enumerate arithGrammar |> take 10 |> List.map (List.reduce (+)) |> List.iter (printfn "%A");;
// "0"
// "1"
// "0+0"
// "0*0"
// "0+1"
// "(0)"
// "1+0"
// "0*1"
// "0+0*0"
// "00"
// ...
module Omega
(**
As F# is not as lazy as Haskell, one need some laziness elixir.
Replacing list, which is inherently lazy in Haskell, with close
relative - stream, to get sufficient laziness in F#.
F#'s own Lazy<T> can be used just as good.
*)
type 'a stream =
| Nil
| Cons of 'a * (unit -> 'a stream)
(* single: 'a -> 'a stream *)
let single e = Cons(e, fun () -> Nil)
(* ofList: 'a list -> 'a stream *)
let rec each = function
| [] -> Nil
| x :: xs -> Cons (x, fun () -> each xs)
(* map: ('a -> 'b') -> 'a stream -> 'b stream *)
let rec map f = function
| Nil -> Nil
| Cons(x, fxs) ->
Cons(f x, fun () -> map f (fxs()))
(* filter: ('a -> bool) -> 'a stream -> 'a stream *)
let rec filter f = function
| Nil -> Nil
| Cons (x, fxs) ->
if f x
then Cons (x, fun () -> filter f (fxs()))
else filter f (fxs())
(* append: 'a stream -> 'a stream -> 'a stream *)
let rec append xs ys =
match xs with
| Nil -> ys
| Cons(x, fxs) ->
Cons(x, fun() -> append (fxs()) ys)
(* concat: 'a stream stream -> 'a stream *)
let rec concat = function
| Nil -> Nil
| Cons(Nil, fxss) -> concat <| fxss()
| Cons(Cons(x, fxs), fxss) ->
Cons(x, fun () -> concat <| Cons((fxs()), fxss))
(* zipCons: 'a stream -> 'a stream stream -> 'a stream stream *)
let rec zipCons xs yss =
match xs, yss with
| Nil, yss -> yss
| xs, Nil -> map single xs
| Cons(x, fxs), Cons(ys, fyss) ->
Cons(Cons(x, fun () -> ys), fun () -> zipCons (fxs()) (fyss()))
(* stripe: 'a stream stream -> 'a stream stream *)
let rec stripe = function
| Nil -> Nil
| Cons(Nil, fxss) -> stripe <| fxss()
| Cons(Cons(x, fxs), fxss) ->
Cons(single x, fun () -> zipCons (fxs()) (stripe (fxss())))
(* diag: 'a stream stream -> 'a stream *)
let diagonal xss = (concat << stripe) xss
(* take: int -> 'a stream -> 'a list *)
let take n s =
let rec take0 n s res =
match n <= 0, s with
| true, _ | _, Nil -> res
| _, Cons (x, fxs) -> take0 (n-1) (fxs()) (x :: res)
take0 n s [] |> List.rev
(**
F# doesn't have typeclasses, so there is no reall need to wrap stream
into separate type to wire the above implementation to the Monad class.
To wire it to `omega` Computational Expression (a kind of instance of a
Monad in F#) lets define `bind` and `return`.
```
instance Monad Omega where
return x = Omega [x]
Omega m >>= f = Omega $ diagonal $ map (runOmega . f) m
```
return === single
bind f m === diagonal $ map f m
*)
(* bind: ('a -> 'b stream) -> 'a stream -> 'b stream *)
let bind f m = diagonal <| map f m
(**
Definition of Omega Computational Expression.
*)
module OmegaBuilder
open Omega
type OmegaBuilder() =
(* For: 'a stream * ('a -> 'b stream) -> 'b stream *)
member x.For (source, body) = bind body source
(* [Yield|Return]: 'a -> 'a stream *)
member x.Yield item = single item
member x.Return item = single item
(* [Yield|Return]From: 'a stream -> 'a stream *)
member x.YieldFrom (source : 'a stream) = source
member x.ReturnFrom (source : 'a stream) = source
(* Zero: unit -> 'a stream *)
member x.Zero () = Nil
(* Delay: (unit -> 'a stream) -> 'a stream *)
member x.Delay (fs : unit -> 'a stream) = fs()
(* Combine: 'a stream * 'a stream -> 'a stream *)
member x.Combine (a, b) = append a b
(* Bind: 'a stream * ('a -> 'b stream) -> 'b stream *)
member x.Bind (source, f) = bind f source
let omega = OmegaBuilder()
open Omega
open OmegaBuilder
(* Fibonaci stream *)
let fib =
let rec fibgen a b =
Cons(a, fun () -> fibgen b (a+b))
fibgen 1 1
// > fib |> take 10;;
// val it : int list = [1; 1; 2; 3; 5; 8; 13; 21; 34; 55]
// >
(* Stream of natural numbers *)
let rec numbersFrom i = Cons (i, fun () -> numbersFrom (i+1))
let rec numbersBetween i j =
if i >= j
then Nil
else Cons (i, fun () -> numbersBetween (i+1) j)
// > numbersFrom 10 |> take 10;;
// val it : int list = [10; 11; 12; 13; 14; 15; 16; 17; 18; 19]
// > numbersBetween 10 13 |> take 10;;
// val it : int list = [10; 11; 12]
// >
(* Stream of streams of natural numbers *)
let rec numberStreamFrom i = Cons (numbersFrom i, fun () -> numberStreamFrom (i+1))
let rec numberStreamBetween i j = Cons (numbersBetween i j, fun () -> numberStreamBetween (i+1) j)
// > numberStreamFrom 3 |> take 3 |> List.map (take 3);;
// val it : int list list = [[3; 4; 5]; [4; 5; 6]; [5; 6; 7]]
// > numberStreamBetween 3 5 |> take 3 |> List.map (take 3);;
// val it : int list list = [[3; 4]; [4]; []]
// >
// `For` finite
let p1 = omega { for i in numbersBetween 0 4 -> i }
// `For For` finite
let p2 = omega { for i in numbersBetween 0 4 do for j in numbersBetween 0 4 do yield (i,j) }
// `For` infinite
let p3 = omega { for i in numbersFrom 0 -> i }
// `For For` infinite
let p4 = omega { for i in numbersFrom 0 do for j in numbersFrom 0 do yield (i,j) }
// `Combine` and `Delay`
let p5 =
omega {
for i in numbersFrom 0 do
for j in numbersFrom 0 do
yield (i, j)
yield (-i, -j)
}
// `Zero`
let p6 =
omega {
for i in numbersFrom 0 do
if i%2 = 0 then
for j in numbersFrom 0 do
if j%2 = 0 then
yield (i,j)
}
// `Bind`
let p7 =
omega {
let! x = numbersFrom 0
return x
}
// `YieldFrom`
let pairs i j name =
omega {
for x in numbersFrom i do
for y in numbersFrom j do
yield (name, x, y)
}
let pairsStreams =
omega {
for i in numbersFrom 0 do
yield! pairs i i (string i)
}
// > pairs 0 0 "P" |> take 10;;
// val it : (string * int * int) list =
// [("P", 0, 0); ("P", 0, 1); ("P", 1, 0); ("P", 0, 2); ("P", 1, 1);
// ("P", 2, 0); ("P", 0, 3); ("P", 1, 2); ("P", 2, 1); ("P", 3, 0)]
// > pairsStreams |> take 10;;
// val it : (string * int * int) list =
// [("0", 0, 0); ("0", 0, 1); ("1", 1, 1); ("0", 1, 0); ("1", 1, 2);
// ("2", 2, 2); ("0", 0, 2); ("1", 2, 1); ("2", 2, 3); ("3", 3, 3)]
// >
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment