Created
October 25, 2014 22:13
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(** | |
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" | |
// ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(** | |
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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