Compare commits

...

4 Commits

Author SHA1 Message Date
Smaug123
f2922a37a4 Generics support 2024-02-19 00:53:06 +00:00
Smaug123
030d8ffa12 Fix one bit of bug 2024-02-18 20:54:20 +00:00
Smaug123
d86bd743af Demonstrate that this is too dumb 2024-02-18 18:37:26 +00:00
Smaug123
dff2431bc8 First pass at handling generics in cata 2024-02-18 18:33:23 +00:00
13 changed files with 648 additions and 372 deletions

View File

@@ -2,8 +2,8 @@ namespace ConsumePlugin
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
type Const = type Const<'a> =
| Int of int | Verbatim of 'a
| String of string | String of string
type PairOpKind = type PairOpKind =
@@ -11,12 +11,12 @@ type PairOpKind =
| ThenDoSeq | ThenDoSeq
[<CreateCatamorphism "TreeCata">] [<CreateCatamorphism "TreeCata">]
type Tree = type Tree<'a, 'b> =
| Const of Const | Const of Const<'a> * 'b
| Pair of Tree * Tree * PairOpKind | Pair of Tree<'a, 'b> * Tree<'a, 'b> * PairOpKind
| Sequential of Tree list | Sequential of Tree<'a, 'b> list
| Builder of Tree * TreeBuilder | Builder of Tree<'a, 'b> * TreeBuilder<'b, 'a>
and TreeBuilder = and TreeBuilder<'b, 'a> =
| Child of TreeBuilder | Child of TreeBuilder<'b, 'a>
| Parent of Tree | Parent of Tree<'a, 'b>

View File

@@ -47,6 +47,10 @@
<Compile Include="GeneratedFileSystem.fs"> <Compile Include="GeneratedFileSystem.fs">
<MyriadFile>FSharpForFunAndProfitCata.fs</MyriadFile> <MyriadFile>FSharpForFunAndProfitCata.fs</MyriadFile>
</Compile> </Compile>
<Compile Include="List.fs" />
<Compile Include="ListCata.fs">
<MyriadFile>List.fs</MyriadFile>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -50,19 +50,3 @@ type Gift =
| Wrapped of Gift * WrappingPaperStyle | Wrapped of Gift * WrappingPaperStyle
| Boxed of Gift | Boxed of Gift
| WithACard of Gift * message : string | WithACard of Gift * message : string
[<CreateCatamorphism "MyListCata">]
type MyList =
| Nil
| Cons of ConsCase
and ConsCase =
{
Head : int
Tail : MyList
}
[<CreateCatamorphism "MyList2Cata">]
type MyList2 =
| Nil
| Cons of int * MyList2

View File

@@ -12,16 +12,16 @@ namespace ConsumePlugin
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
/// Description of how to combine cases during a fold /// Description of how to combine cases during a fold
type TreeBuilderCataCase<'TreeBuilder, 'Tree> = type TreeBuilderCataCase<'b, 'a, 'TreeBuilder, 'Tree> =
/// How to operate on the Child case /// How to operate on the Child case
abstract Child : 'TreeBuilder -> 'TreeBuilder abstract Child : 'TreeBuilder -> 'TreeBuilder
/// How to operate on the Parent case /// How to operate on the Parent case
abstract Parent : 'Tree -> 'TreeBuilder abstract Parent : 'Tree -> 'TreeBuilder
/// Description of how to combine cases during a fold /// Description of how to combine cases during a fold
type TreeCataCase<'TreeBuilder, 'Tree> = type TreeCataCase<'a, 'b, 'TreeBuilder, 'Tree> =
/// How to operate on the Const case /// How to operate on the Const case
abstract Const : Const -> 'Tree abstract Const : Const<'a> -> 'b -> 'Tree
/// How to operate on the Pair case /// How to operate on the Pair case
abstract Pair : 'Tree -> 'Tree -> PairOpKind -> 'Tree abstract Pair : 'Tree -> 'Tree -> PairOpKind -> 'Tree
/// How to operate on the Sequential case /// How to operate on the Sequential case
@@ -30,30 +30,30 @@ type TreeCataCase<'TreeBuilder, 'Tree> =
abstract Builder : 'Tree -> 'TreeBuilder -> 'Tree abstract Builder : 'Tree -> 'TreeBuilder -> 'Tree
/// Specifies how to perform a fold (catamorphism) over the type Tree and its friends. /// Specifies how to perform a fold (catamorphism) over the type Tree and its friends.
type TreeCata<'TreeBuilder, 'Tree> = type TreeCata<'b, 'a, 'TreeBuilder, 'Tree> =
{ {
/// How to perform a fold (catamorphism) over the type TreeBuilder /// How to perform a fold (catamorphism) over the type TreeBuilder
TreeBuilder : TreeBuilderCataCase<'TreeBuilder, 'Tree> TreeBuilder : TreeBuilderCataCase<'b, 'a, 'TreeBuilder, 'Tree>
/// How to perform a fold (catamorphism) over the type Tree /// How to perform a fold (catamorphism) over the type Tree
Tree : TreeCataCase<'TreeBuilder, 'Tree> Tree : TreeCataCase<'a, 'b, 'TreeBuilder, 'Tree>
} }
/// Methods to perform a catamorphism over the type Tree /// Methods to perform a catamorphism over the type Tree
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module TreeCata = module TreeCata =
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
type private Instruction = type private Instruction<'b, 'a> =
| Process__TreeBuilder of TreeBuilder | Process__TreeBuilder of TreeBuilder<'b, 'a>
| Process__Tree of Tree | Process__Tree of Tree<'a, 'b>
| TreeBuilder_Child | TreeBuilder_Child
| TreeBuilder_Parent | TreeBuilder_Parent
| Tree_Pair of PairOpKind | Tree_Pair of PairOpKind
| Tree_Sequential of int | Tree_Sequential of int
| Tree_Builder | Tree_Builder
let private loop (cata : TreeCata<_, _>) (instructions : ResizeArray<Instruction>) = let private loop (cata : TreeCata<'b, 'a, 'TreeBuilder, 'Tree>) (instructions : ResizeArray<Instruction<'b, 'a>>) =
let treeStack = ResizeArray () let treeStack = ResizeArray<'Tree> ()
let treeBuilderStack = ResizeArray () let treeBuilderStack = ResizeArray<'TreeBuilder> ()
while instructions.Count > 0 do while instructions.Count > 0 do
let currentInstruction = instructions.[instructions.Count - 1] let currentInstruction = instructions.[instructions.Count - 1]
@@ -70,7 +70,7 @@ module TreeCata =
instructions.Add (Instruction.Process__Tree arg0_0) instructions.Add (Instruction.Process__Tree arg0_0)
| Instruction.Process__Tree x -> | Instruction.Process__Tree x ->
match x with match x with
| Tree.Const (arg0_0) -> cata.Tree.Const arg0_0 |> treeStack.Add | Tree.Const (arg0_0, arg1_0) -> cata.Tree.Const arg0_0 arg1_0 |> treeStack.Add
| Tree.Pair (arg0_0, arg1_0, arg2_0) -> | Tree.Pair (arg0_0, arg1_0, arg2_0) ->
instructions.Add (Instruction.Tree_Pair (arg2_0)) instructions.Add (Instruction.Tree_Pair (arg2_0))
instructions.Add (Instruction.Process__Tree arg0_0) instructions.Add (Instruction.Process__Tree arg0_0)
@@ -120,14 +120,18 @@ module TreeCata =
treeBuilderStack, treeStack treeBuilderStack, treeStack
/// Execute the catamorphism. /// Execute the catamorphism.
let runTreeBuilder (cata : TreeCata<'TreeBuilderRet, 'TreeRet>) (x : TreeBuilder) : 'TreeBuilderRet = let runTreeBuilder
(cata : TreeCata<'b, 'a, 'TreeBuilderRet, 'TreeRet>)
(x : TreeBuilder<'b, 'a>)
: 'TreeBuilderRet
=
let instructions = ResizeArray () let instructions = ResizeArray ()
instructions.Add (Instruction.Process__TreeBuilder x) instructions.Add (Instruction.Process__TreeBuilder x)
let treeBuilderRetStack, treeRetStack = loop cata instructions let treeBuilderRetStack, treeRetStack = loop cata instructions
Seq.exactlyOne treeBuilderRetStack Seq.exactlyOne treeBuilderRetStack
/// Execute the catamorphism. /// Execute the catamorphism.
let runTree (cata : TreeCata<'TreeBuilderRet, 'TreeRet>) (x : Tree) : 'TreeRet = let runTree (cata : TreeCata<'b, 'a, 'TreeBuilderRet, 'TreeRet>) (x : Tree<'a, 'b>) : 'TreeRet =
let instructions = ResizeArray () let instructions = ResizeArray ()
instructions.Add (Instruction.Process__Tree x) instructions.Add (Instruction.Process__Tree x)
let treeBuilderRetStack, treeRetStack = loop cata instructions let treeBuilderRetStack, treeRetStack = loop cata instructions

View File

@@ -33,8 +33,8 @@ module FileSystemItemCata =
| Process__FileSystemItem of FileSystemItem | Process__FileSystemItem of FileSystemItem
| FileSystemItem_Directory of string * int * int | FileSystemItem_Directory of string * int * int
let private loop (cata : FileSystemCata<_>) (instructions : ResizeArray<Instruction>) = let private loop (cata : FileSystemCata<'FileSystemItem>) (instructions : ResizeArray<Instruction>) =
let fileSystemItemStack = ResizeArray () let fileSystemItemStack = ResizeArray<'FileSystemItem> ()
while instructions.Count > 0 do while instructions.Count > 0 do
let currentInstruction = instructions.[instructions.Count - 1] let currentInstruction = instructions.[instructions.Count - 1]
@@ -108,8 +108,8 @@ module GiftCata =
| Gift_Boxed | Gift_Boxed
| Gift_WithACard of string | Gift_WithACard of string
let private loop (cata : GiftCata<_>) (instructions : ResizeArray<Instruction>) = let private loop (cata : GiftCata<'Gift>) (instructions : ResizeArray<Instruction>) =
let giftStack = ResizeArray () let giftStack = ResizeArray<'Gift> ()
while instructions.Count > 0 do while instructions.Count > 0 do
let currentInstruction = instructions.[instructions.Count - 1] let currentInstruction = instructions.[instructions.Count - 1]
@@ -150,112 +150,3 @@ module GiftCata =
instructions.Add (Instruction.Process__Gift x) instructions.Add (Instruction.Process__Gift x)
let giftRetStack = loop cata instructions let giftRetStack = loop cata instructions
Seq.exactlyOne giftRetStack Seq.exactlyOne giftRetStack
namespace ConsumePlugin
open WoofWare.Myriad.Plugins
/// Description of how to combine cases during a fold
type MyListCataCase<'MyList> =
/// How to operate on the Nil case
abstract Nil : 'MyList
/// How to operate on the Cons case
abstract Cons : head : int -> tail : 'MyList -> 'MyList
/// Specifies how to perform a fold (catamorphism) over the type MyList and its friends.
type MyListCata<'MyList> =
{
/// How to perform a fold (catamorphism) over the type MyList
MyList : MyListCataCase<'MyList>
}
/// Methods to perform a catamorphism over the type MyList
[<RequireQualifiedAccess>]
module MyListCata =
[<RequireQualifiedAccess>]
type private Instruction =
| Process__MyList of MyList
| MyList_Cons of int
let private loop (cata : MyListCata<_>) (instructions : ResizeArray<Instruction>) =
let myListStack = ResizeArray ()
while instructions.Count > 0 do
let currentInstruction = instructions.[instructions.Count - 1]
instructions.RemoveAt (instructions.Count - 1)
match currentInstruction with
| Instruction.Process__MyList x ->
match x with
| MyList.Nil -> cata.MyList.Nil |> myListStack.Add
| MyList.Cons ({
Head = head
Tail = tail
}) ->
instructions.Add (Instruction.MyList_Cons (head))
instructions.Add (Instruction.Process__MyList tail)
| Instruction.MyList_Cons (head) ->
let tail = myListStack.[myListStack.Count - 1]
myListStack.RemoveAt (myListStack.Count - 1)
cata.MyList.Cons head tail |> myListStack.Add
myListStack
/// Execute the catamorphism.
let runMyList (cata : MyListCata<'MyListRet>) (x : MyList) : 'MyListRet =
let instructions = ResizeArray ()
instructions.Add (Instruction.Process__MyList x)
let myListRetStack = loop cata instructions
Seq.exactlyOne myListRetStack
namespace ConsumePlugin
open WoofWare.Myriad.Plugins
/// Description of how to combine cases during a fold
type MyList2CataCase<'MyList2> =
/// How to operate on the Nil case
abstract Nil : 'MyList2
/// How to operate on the Cons case
abstract Cons : int -> 'MyList2 -> 'MyList2
/// Specifies how to perform a fold (catamorphism) over the type MyList2 and its friends.
type MyList2Cata<'MyList2> =
{
/// How to perform a fold (catamorphism) over the type MyList2
MyList2 : MyList2CataCase<'MyList2>
}
/// Methods to perform a catamorphism over the type MyList2
[<RequireQualifiedAccess>]
module MyList2Cata =
[<RequireQualifiedAccess>]
type private Instruction =
| Process__MyList2 of MyList2
| MyList2_Cons of int
let private loop (cata : MyList2Cata<_>) (instructions : ResizeArray<Instruction>) =
let myList2Stack = ResizeArray ()
while instructions.Count > 0 do
let currentInstruction = instructions.[instructions.Count - 1]
instructions.RemoveAt (instructions.Count - 1)
match currentInstruction with
| Instruction.Process__MyList2 x ->
match x with
| MyList2.Nil -> cata.MyList2.Nil |> myList2Stack.Add
| MyList2.Cons (arg0_0, arg1_0) ->
instructions.Add (Instruction.MyList2_Cons (arg0_0))
instructions.Add (Instruction.Process__MyList2 arg1_0)
| Instruction.MyList2_Cons (arg0_0) ->
let arg1_0 = myList2Stack.[myList2Stack.Count - 1]
myList2Stack.RemoveAt (myList2Stack.Count - 1)
cata.MyList2.Cons arg0_0 arg1_0 |> myList2Stack.Add
myList2Stack
/// Execute the catamorphism.
let runMyList2 (cata : MyList2Cata<'MyList2Ret>) (x : MyList2) : 'MyList2Ret =
let instructions = ResizeArray ()
instructions.Add (Instruction.Process__MyList2 x)
let myList2RetStack = loop cata instructions
Seq.exactlyOne myList2RetStack

19
ConsumePlugin/List.fs Normal file
View File

@@ -0,0 +1,19 @@
namespace ConsumePlugin
open WoofWare.Myriad.Plugins
[<CreateCatamorphism "MyListCata">]
type MyList<'a> =
| Nil
| Cons of ConsCase<'a>
and ConsCase<'a> =
{
Head : 'a
Tail : MyList<'a>
}
[<CreateCatamorphism "MyList2Cata">]
type MyList2<'a> =
| Nil
| Cons of 'a * MyList2<'a>

118
ConsumePlugin/ListCata.fs Normal file
View File

@@ -0,0 +1,118 @@
//------------------------------------------------------------------------------
// This code was generated by myriad.
// Changes to this file will be lost when the code is regenerated.
//------------------------------------------------------------------------------
namespace ConsumePlugin
open WoofWare.Myriad.Plugins
/// Description of how to combine cases during a fold
type MyListCataCase<'a, 'MyList> =
/// How to operate on the Nil case
abstract Nil : 'MyList
/// How to operate on the Cons case
abstract Cons : head : 'a -> tail : 'MyList -> 'MyList
/// Specifies how to perform a fold (catamorphism) over the type MyList and its friends.
type MyListCata<'a, 'MyList> =
{
/// How to perform a fold (catamorphism) over the type MyList
MyList : MyListCataCase<'a, 'MyList>
}
/// Methods to perform a catamorphism over the type MyList
[<RequireQualifiedAccess>]
module MyListCata =
[<RequireQualifiedAccess>]
type private Instruction<'a> =
| Process__MyList of MyList<'a>
| MyList_Cons of 'a
let private loop (cata : MyListCata<'a, 'MyList>) (instructions : ResizeArray<Instruction<'a>>) =
let myListStack = ResizeArray<'MyList> ()
while instructions.Count > 0 do
let currentInstruction = instructions.[instructions.Count - 1]
instructions.RemoveAt (instructions.Count - 1)
match currentInstruction with
| Instruction.Process__MyList x ->
match x with
| MyList.Nil -> cata.MyList.Nil |> myListStack.Add
| MyList.Cons ({
Head = head
Tail = tail
}) ->
instructions.Add (Instruction.MyList_Cons (head))
instructions.Add (Instruction.Process__MyList tail)
| Instruction.MyList_Cons (head) ->
let tail = myListStack.[myListStack.Count - 1]
myListStack.RemoveAt (myListStack.Count - 1)
cata.MyList.Cons head tail |> myListStack.Add
myListStack
/// Execute the catamorphism.
let runMyList (cata : MyListCata<'a, 'MyListRet>) (x : MyList<'a>) : 'MyListRet =
let instructions = ResizeArray ()
instructions.Add (Instruction.Process__MyList x)
let myListRetStack = loop cata instructions
Seq.exactlyOne myListRetStack
namespace ConsumePlugin
open WoofWare.Myriad.Plugins
/// Description of how to combine cases during a fold
type MyList2CataCase<'a, 'MyList2> =
/// How to operate on the Nil case
abstract Nil : 'MyList2
/// How to operate on the Cons case
abstract Cons : 'a -> 'MyList2 -> 'MyList2
/// Specifies how to perform a fold (catamorphism) over the type MyList2 and its friends.
type MyList2Cata<'a, 'MyList2> =
{
/// How to perform a fold (catamorphism) over the type MyList2
MyList2 : MyList2CataCase<'a, 'MyList2>
}
/// Methods to perform a catamorphism over the type MyList2
[<RequireQualifiedAccess>]
module MyList2Cata =
[<RequireQualifiedAccess>]
type private Instruction<'a> =
| Process__MyList2 of MyList2<'a>
| MyList2_Cons of 'a
let private loop (cata : MyList2Cata<'a, 'MyList2>) (instructions : ResizeArray<Instruction<'a>>) =
let myList2Stack = ResizeArray<'MyList2> ()
while instructions.Count > 0 do
let currentInstruction = instructions.[instructions.Count - 1]
instructions.RemoveAt (instructions.Count - 1)
match currentInstruction with
| Instruction.Process__MyList2 x ->
match x with
| MyList2.Nil -> cata.MyList2.Nil |> myList2Stack.Add
| MyList2.Cons (arg0_0, arg1_0) ->
instructions.Add (Instruction.MyList2_Cons (arg0_0))
instructions.Add (Instruction.Process__MyList2 arg1_0)
| Instruction.MyList2_Cons (arg0_0) ->
let arg1_0 = myList2Stack.[myList2Stack.Count - 1]
myList2Stack.RemoveAt (myList2Stack.Count - 1)
cata.MyList2.Cons arg0_0 arg1_0 |> myList2Stack.Add
myList2Stack
/// Execute the catamorphism.
let runMyList2 (cata : MyList2Cata<'a, 'MyList2Ret>) (x : MyList2<'a>) : 'MyList2Ret =
let instructions = ResizeArray ()
instructions.Add (Instruction.Process__MyList2 x)
let myList2RetStack = loop cata instructions
Seq.exactlyOne myList2RetStack

View File

@@ -332,7 +332,7 @@ thereby allowing the programmer to use F#'s record-update syntax.
Takes a collection of mutually recursive discriminated unions: Takes a collection of mutually recursive discriminated unions:
```fsharp ```fsharp
[<CreateCatamorphism>] [<CreateCatamorphism "MyCata">]
type Expr = type Expr =
| Const of Const | Const of Const
| Pair of Expr * Expr * PairOpKind | Pair of Expr * Expr * PairOpKind
@@ -356,7 +356,7 @@ type ExprBuilderCata<'Expr, 'ExprBuilder> =
abstract Child : 'ExprBuilder -> 'ExprBuilder abstract Child : 'ExprBuilder -> 'ExprBuilder
abstract Parent : 'Expr -> 'ExprBuilder abstract Parent : 'Expr -> 'ExprBuilder
type Cata<'Expr, 'ExprBuilder> = type MyCata<'Expr, 'ExprBuilder> =
{ {
Expr : ExprCata<'Expr, 'ExprBuilder> Expr : ExprCata<'Expr, 'ExprBuilder>
ExprBuilder : ExprBuilderCata<'Expr, 'ExprBuilder> ExprBuilder : ExprBuilderCata<'Expr, 'ExprBuilder>
@@ -364,10 +364,10 @@ type Cata<'Expr, 'ExprBuilder> =
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module ExprCata = module ExprCata =
let runExpr (cata : Cata<'ExprRet, 'ExprBuilderRet>) (x : Expr) : 'ExprRet = let runExpr (cata : MyCata<'ExprRet, 'ExprBuilderRet>) (x : Expr) : 'ExprRet =
failwith "this is implemented" failwith "this is implemented"
let runExprBuilder (cata : Cata<'ExprRet, 'ExprBuilderRet>) (x : ExprBuilder) : 'ExprBuilderRet = let runExprBuilder (cata : MyCata<'ExprRet, 'ExprBuilderRet>) (x : ExprBuilder) : 'ExprBuilderRet =
failwith "this is implemented" failwith "this is implemented"
``` ```
@@ -381,6 +381,10 @@ and then each time you only plug in what you want to do.
* Mutually recursive DUs are supported (as in the example above). * Mutually recursive DUs are supported (as in the example above).
Every DU in a recursive `type Foo... and Bar...` knot will be given an appropriate cata, as long as any one of those DUs has the `[<CreateCatamorphism>]` attribute. Every DU in a recursive `type Foo... and Bar...` knot will be given an appropriate cata, as long as any one of those DUs has the `[<CreateCatamorphism>]` attribute.
* There is *limited* support for records and for lists. * There is *limited* support for records and for lists.
* There is *extremely brittle* support for generics in the DUs you are cata'ing over.
It is based on the names of the generic parameters, so you must ensure that generic parameters with the same name have the same meaning across the various cases in your recursive knot of DUs.
(If you overstep the bounds of what this generator can do, you will get compile-time errors, e.g. with generics being constrained to each other's values.)
See the [List tests](./WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList2.fs) for an example, where we re-implement `FSharpList<'a>`.
### Limitations ### Limitations

View File

@@ -8,17 +8,17 @@ open FsCheck
[<TestFixture>] [<TestFixture>]
module TestCataGenerator = module TestCataGenerator =
let idCata : TreeCata<_, _> = let idCata<'a, 'b> : TreeCata<'a, 'b, _, _> =
{ {
Tree = Tree =
{ new TreeCataCase<_, _> with { new TreeCataCase<_, _, _, _> with
member _.Const x = Const x member _.Const x y = Const (x, y)
member _.Pair x y z = Pair (x, y, z) member _.Pair x y z = Pair (x, y, z)
member _.Sequential xs = Sequential xs member _.Sequential xs = Sequential xs
member _.Builder x b = Builder (x, b) member _.Builder x b = Builder (x, b)
} }
TreeBuilder = TreeBuilder =
{ new TreeBuilderCataCase<_, _> with { new TreeBuilderCataCase<_, _, _, _> with
member _.Child x = Child x member _.Child x = Child x
member _.Parent x = Parent x member _.Parent x = Parent x
} }
@@ -27,7 +27,7 @@ module TestCataGenerator =
[<Test>] [<Test>]
let ``Example`` () = let ``Example`` () =
let x = let x =
Tree.Pair (Tree.Const (Const.Int 0), Tree.Const (Const.String ""), PairOpKind.ThenDoSeq) Tree.Pair (Tree.Const (Const.Verbatim 0, "hi"), Tree.Const (Const.String "", "bye"), PairOpKind.ThenDoSeq)
TreeCata.runTree idCata x |> shouldEqual x TreeCata.runTree idCata x |> shouldEqual x
@@ -36,7 +36,7 @@ module TestCataGenerator =
let ``Cata works`` () = let ``Cata works`` () =
let builderCases = ref 0 let builderCases = ref 0
let property (x : Tree) = let property (x : Tree<int, string>) =
match x with match x with
| Tree.Builder _ -> Interlocked.Increment builderCases |> ignore | Tree.Builder _ -> Interlocked.Increment builderCases |> ignore
| _ -> () | _ -> ()

View File

@@ -8,10 +8,10 @@ open ConsumePlugin
[<TestFixture>] [<TestFixture>]
module TestMyList = module TestMyList =
let idCata : MyListCata<_> = let idCata<'a> : MyListCata<'a, _> =
{ {
MyList = MyList =
{ new MyListCataCase<_> with { new MyListCataCase<'a, _> with
member _.Nil = MyList.Nil member _.Nil = MyList.Nil
member _.Cons head tail = member _.Cons head tail =
@@ -21,36 +21,32 @@ module TestMyList =
Tail = tail Tail = tail
} }
} }
} }
[<Test>] [<Test>]
let ``Cata works`` () = let ``Cata works`` () =
let property (x : MyList) = MyListCata.runMyList idCata x = x let property (x : MyList<int>) = MyListCata.runMyList idCata x = x
Check.QuickThrowOnFailure property Check.QuickThrowOnFailure property
let toListCata = let toListCata<'a> =
{ {
MyList = MyList =
{ new MyListCataCase<int list> with { new MyListCataCase<'a, 'a list> with
member _.Nil = [] member _.Nil = []
member _.Cons (head : int) (tail : int list) = head :: tail member _.Cons (head : 'a) (tail : 'a list) = head :: tail
} }
} }
let toListViaCata (l : MyList) : int list = MyListCata.runMyList toListCata l let toListViaCata<'a> (l : MyList<'a>) : 'a list = MyListCata.runMyList toListCata l
[<Test>] [<Test>]
let ``Example of a fold converting to a new data structure`` () = let ``Example of a fold converting to a new data structure`` () =
let rec toListNaive (l : MyList) : int list = let rec toListNaive (l : MyList<int>) : int list =
match l with match l with
| MyList.Nil -> [] | MyList.Nil -> []
| MyList.Cons { | MyList.Cons consCell -> consCell.Head :: toListNaive consCell.Tail
Head = head
Tail = tail
} -> head :: toListNaive tail
Check.QuickThrowOnFailure (fun l -> toListNaive l = toListViaCata l) Check.QuickThrowOnFailure (fun l -> toListNaive l = toListViaCata l)
@@ -62,20 +58,20 @@ module TestMyList =
let sumCata = let sumCata =
{ {
MyList = MyList =
{ new MyListCataCase<int64> with { new MyListCataCase<int, int64> with
member _.Nil = baseCase member _.Nil = baseCase
member _.Cons (head : int) (tail : int64) = atLeaf head tail member _.Cons (head : int) (tail : int64) = atLeaf head tail
} }
} }
let viaCata (l : MyList) : int64 = MyListCata.runMyList sumCata l let viaCata (l : MyList<int>) : int64 = MyListCata.runMyList sumCata l
let viaFold (l : MyList) : int64 = let viaFold (l : MyList<int>) : int64 =
// choose your favourite "to list" method - here I use the cata // choose your favourite "to list" method - here I use the cata
// but that could have been done naively // but that could have been done naively
(toListViaCata l, baseCase) (toListViaCata l, baseCase)
||> List.foldBack (fun elt state -> atLeaf elt state) ||> List.foldBack (fun elt state -> atLeaf elt state)
let property (l : MyList) = viaCata l = viaFold l let property (l : MyList<int>) = viaCata l = viaFold l
Check.QuickThrowOnFailure property Check.QuickThrowOnFailure property

View File

@@ -8,19 +8,18 @@ open ConsumePlugin
[<TestFixture>] [<TestFixture>]
module TestMyList2 = module TestMyList2 =
let idCata : MyList2Cata<_> = let idCata<'a> : MyList2Cata<'a, _> =
{ {
MyList2 = MyList2 =
{ new MyList2CataCase<_> with { new MyList2CataCase<'a, _> with
member _.Nil = MyList2.Nil member _.Nil = MyList2.Nil
member _.Cons head tail = MyList2.Cons (head, tail) member _.Cons (head : 'a) (tail : MyList2<'a>) = MyList2.Cons (head, tail)
} }
} }
[<Test>] [<Test>]
let ``Cata works`` () = let ``Cata works`` () =
let property (x : MyList2) = MyList2Cata.runMyList2 idCata x = x let property (x : MyList2<int>) = MyList2Cata.runMyList2 idCata x = x
Check.QuickThrowOnFailure property Check.QuickThrowOnFailure property

View File

@@ -76,6 +76,9 @@ type internal AdtNode =
{ {
Type : SynType Type : SynType
Name : Ident option Name : Ident option
/// An ordered list, so you can look up any given generic within `this.Type`
/// to discover what its index is in the parent DU which defined it.
GenericsOfParent : SynTyparDecl list
} }
/// A DU is a sum of products (e.g. `type Thing = Foo of a * b`); /// A DU is a sum of products (e.g. `type Thing = Foo of a * b`);
@@ -85,6 +88,10 @@ type internal AdtProduct =
{ {
Name : SynIdent Name : SynIdent
Fields : AdtNode list Fields : AdtNode list
/// This AdtProduct represents a product in which there might be
/// some bound type parameters. This field lists the bound
/// type parameters in the order they appeared on the parent type.
Generics : SynTyparDecl list
} }
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
@@ -400,29 +407,65 @@ module internal AstHelper =
Accessibility = accessibility Accessibility = accessibility
} }
let getUnionCases (SynTypeDefn.SynTypeDefn (_, repr, _, _, _, _)) : AdtProduct list = let getUnionCases
(SynTypeDefn.SynTypeDefn (info, repr, _, _, _, _))
: AdtProduct list * SynTyparDecl list * SynAccess option
=
let typars, access =
match info with
| SynComponentInfo (_, typars, _, _, _, _, access, _) -> typars, access
let typars =
match typars with
| None -> []
| Some (SynTyparDecls.PrefixList (decls, _)) -> decls
| Some (SynTyparDecls.SinglePrefix (l, _)) -> [ l ]
| Some (SynTyparDecls.PostfixList (decls, constraints, _)) ->
if not constraints.IsEmpty then
failwith "Constrained type parameters not currently supported"
decls
match repr with match repr with
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union (_, cases, _), _) -> | SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union (_, cases, _), _) ->
cases let cases =
|> List.map (fun (SynUnionCase.SynUnionCase (_, ident, kind, _, _, _, _)) -> cases
match kind with |> List.map (fun (SynUnionCase.SynUnionCase (_, ident, kind, _, _, _, _)) ->
| SynUnionCaseKind.FullType _ -> failwith "FullType union cases not supported" match kind with
| SynUnionCaseKind.Fields fields -> | SynUnionCaseKind.FullType _ -> failwith "FullType union cases not supported"
{ | SynUnionCaseKind.Fields fields ->
Name = ident {
Fields = Name = ident
fields Fields =
|> List.map (fun (SynField.SynField (_, _, id, ty, _, _, _, _, _)) -> fields
{ |> List.map (fun (SynField.SynField (_, _, id, ty, _, _, _, _, _)) ->
Type = ty {
Name = id Type = ty
} Name = id
) GenericsOfParent = typars
} }
) )
Generics = typars
}
)
cases, typars, access
| _ -> failwithf "Failed to get union cases for type that was: %+A" repr | _ -> failwithf "Failed to get union cases for type that was: %+A" repr
let getRecordFields (SynTypeDefn.SynTypeDefn (_, repr, _, _, _, _)) : AdtNode list = let getRecordFields (SynTypeDefn.SynTypeDefn (typeInfo, repr, _, _, _, _)) : AdtNode list =
let (SynComponentInfo.SynComponentInfo (typeParams = typars)) = typeInfo
let typars =
match typars with
| None -> []
| Some (SynTyparDecls.PrefixList (decls, _)) -> decls
| Some (SynTyparDecls.SinglePrefix (l, _)) -> [ l ]
| Some (SynTyparDecls.PostfixList (decls, constraints, _)) ->
if not constraints.IsEmpty then
failwith "Constrained type parameters not currently supported"
decls
match repr with match repr with
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (_, fields, _), _) -> | SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (_, fields, _), _) ->
fields fields
@@ -430,6 +473,7 @@ module internal AstHelper =
{ {
Name = ident Name = ident
Type = ty Type = ty
GenericsOfParent = typars
} }
) )
| _ -> failwithf "Failed to get record elements for type that was: %+A" repr | _ -> failwithf "Failed to get record elements for type that was: %+A" repr

View File

@@ -35,6 +35,10 @@ module internal CataGenerator =
/// The relationship this field has with the parent type (or the /// The relationship this field has with the parent type (or the
/// recursive knot of parent types) /// recursive knot of parent types)
Description : FieldDescription Description : FieldDescription
/// Any generic parameters this field consumes.
/// This only makes sense in the context of a UnionAnalysis:
/// it is an index into the parent Union's collection of generic parameters.
RequiredGenerics : int list option
} }
type CataUnionRecordField = (Ident * CataUnionBasicField) list type CataUnionRecordField = (Ident * CataUnionBasicField) list
@@ -81,6 +85,8 @@ module internal CataGenerator =
/// recursive knot), this is everything we need to know about it for the cata. /// recursive knot), this is everything we need to know about it for the cata.
type UnionAnalysis = type UnionAnalysis =
{ {
Accessibility : SynAccess option
Typars : SynTyparDecl list
/// The name of the stack we'll use for the results /// The name of the stack we'll use for the results
/// of returning from a descent into this union type, /// of returning from a descent into this union type,
/// when performing the cata /// when performing the cata
@@ -112,28 +118,70 @@ module internal CataGenerator =
/// Seq.exactlyOne {relevantTypar}Stack /// Seq.exactlyOne {relevantTypar}Stack
let createRunFunction let createRunFunction
(cataName : Ident) (cataName : Ident)
(allTypars : SynType list) (userProvidedTypars : SynTyparDecl list)
(allArtificialTypars : SynType list)
(relevantTypar : SynType) (relevantTypar : SynType)
(unionType : SynTypeDefn) (analysis : UnionAnalysis)
: SynBinding : SynBinding
= =
let relevantTypeName = let relevantTypeName = analysis.ParentTypeName
match unionType with
| SynTypeDefn.SynTypeDefn (SynComponentInfo.SynComponentInfo (longId = id), _, _, _, _, _) -> List.last id
let allTyparNames = let allArtificialTyparNames =
allTypars allArtificialTypars
|> List.map (fun ty -> |> List.map (fun ty ->
match ty with match ty with
| SynType.Var (SynTypar.SynTypar (ident = ident), _) -> ident | SynType.Var (SynTypar.SynTypar (ident = ident), _) -> ident
| _ -> failwith "logic error in generator" | _ -> failwith "logic error in generator"
) )
let userProvidedTyparsForCase =
analysis.Typars
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, ty)) -> SynType.Var (ty, range0))
let userProvidedTyparsForCata =
userProvidedTypars
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, ty)) -> SynType.Var (ty, range0))
let relevantTyparName = let relevantTyparName =
match relevantTypar with match relevantTypar with
| SynType.Var (SynTypar.SynTypar (ident = ident), _) -> ident | SynType.Var (SynTypar.SynTypar (ident = ident), _) -> ident
| _ -> failwith "logic error in generator" | _ -> failwith "logic error in generator"
let inputObjectType =
let baseType =
SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent relevantTypeName)
if userProvidedTypars.Length = 0 then
baseType
else
SynType.App (
baseType,
Some range0,
userProvidedTyparsForCase,
List.replicate (userProvidedTypars.Length - 1) range0,
Some range0,
false,
range0
)
// The object on which we'll run the cata
let inputObject =
SynPat.CreateTyped (SynPat.CreateNamed (Ident.Create "x"), inputObjectType)
let cataObject =
SynPat.CreateTyped (
SynPat.CreateNamed (Ident.Create "cata"),
SynType.App (
SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent [ cataName ]),
Some range0,
userProvidedTyparsForCata @ allArtificialTypars,
List.replicate (userProvidedTypars.Length + allArtificialTypars.Length - 1) range0,
Some range0,
false,
range0
)
)
SynBinding.SynBinding ( SynBinding.SynBinding (
None, None,
SynBindingKind.Normal, SynBindingKind.Normal,
@@ -150,29 +198,8 @@ module internal CataGenerator =
None None
), ),
SynPat.CreateLongIdent ( SynPat.CreateLongIdent (
SynLongIdent.CreateString ("run" + relevantTypeName.idText), SynLongIdent.CreateString ("run" + List.last(relevantTypeName).idText),
[ [ SynPat.CreateParen cataObject ; SynPat.CreateParen inputObject ]
SynPat.CreateParen (
SynPat.CreateTyped (
SynPat.CreateNamed (Ident.Create "cata"),
SynType.App (
SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent [ cataName ]),
Some range0,
allTypars,
List.replicate (allTypars.Length - 1) range0,
Some range0,
false,
range0
)
)
)
SynPat.CreateParen (
SynPat.CreateTyped (
SynPat.CreateNamed (Ident.Create "x"),
SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent [ relevantTypeName ])
)
)
]
), ),
Some (SynBindingReturnInfo.Create relevantTypar), Some (SynBindingReturnInfo.Create relevantTypar),
SynExpr.CreateTyped ( SynExpr.CreateTyped (
@@ -196,10 +223,7 @@ module internal CataGenerator =
SynExpr.CreateLongIdent (SynLongIdent.Create [ "instructions" ; "Add" ]), SynExpr.CreateLongIdent (SynLongIdent.Create [ "instructions" ; "Add" ]),
SynExpr.CreateParen ( SynExpr.CreateParen (
SynExpr.CreateApp ( SynExpr.CreateApp (
SynExpr.CreateLongIdent ( SynExpr.CreateLongIdent analysis.AssociatedProcessInstruction,
SynLongIdent.Create
[ "Instruction" ; "Process__" + relevantTypeName.idText ]
),
SynExpr.CreateLongIdent (SynLongIdent.CreateString "x") SynExpr.CreateLongIdent (SynLongIdent.CreateString "x")
) )
) )
@@ -219,8 +243,8 @@ module internal CataGenerator =
Ident.Create (t.idText + "Stack") |> Ident.lowerFirstLetter Ident.Create (t.idText + "Stack") |> Ident.lowerFirstLetter
) )
) )
allTyparNames, allArtificialTyparNames,
List.replicate (allTypars.Length - 1) range0, List.replicate (allArtificialTyparNames.Length - 1) range0,
range0 range0
), ),
expr = expr =
@@ -262,9 +286,10 @@ module internal CataGenerator =
match ty with match ty with
| SynTypeDefn.SynTypeDefn (SynComponentInfo.SynComponentInfo (_, _, _, id, _, _, _, _), _, _, _, _, _) -> id | SynTypeDefn.SynTypeDefn (SynComponentInfo.SynComponentInfo (_, _, _, id, _, _, _, _), _, _, _, _, _) -> id
let getNameUnion (unionType : SynType) : LongIdent option = let rec getNameUnion (unionType : SynType) : LongIdent option =
match unionType with match unionType with
| SynType.LongIdent (SynLongIdent.SynLongIdent (name, _, _)) -> Some name | SynType.LongIdent (SynLongIdent.SynLongIdent (name, _, _)) -> Some name
| SynType.App (ty, _, _, _, _, _, _) -> getNameUnion ty
| _ -> None | _ -> None
let getNameKey (ty : SynTypeDefn) : string = let getNameKey (ty : SynTypeDefn) : string =
@@ -279,51 +304,20 @@ module internal CataGenerator =
/// Get the fields of this particular union case, and describe their relation to the /// Get the fields of this particular union case, and describe their relation to the
/// recursive knot of user-provided DUs for which we are creating a cata. /// recursive knot of user-provided DUs for which we are creating a cata.
let analyse let analyse
(availableGenerics : SynTyparDecl list)
(allRecordTypes : SynTypeDefn list) (allRecordTypes : SynTypeDefn list)
(allUnionTypes : SynTypeDefn list) (allUnionTypes : SynTypeDefn list)
(argIndex : int) (argIndex : int)
(fields : AdtNode list) (fields : AdtNode list)
: CataUnionBasicField list : CataUnionBasicField list
= =
let availableGenerics =
availableGenerics
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, SynTypar.SynTypar (ident, _, _))) -> ident)
let rec go (prefix : string) (name : Ident option) (ty : SynType) : CataUnionBasicField = let rec go (prefix : string) (name : Ident option) (ty : SynType) : CataUnionBasicField =
let stripped = SynType.stripOptionalParen ty let dealWithPrimitive (typeArgs : int list option) (ty : SynType) (typeName : LongIdent) =
let key = typeName |> List.map _.idText |> String.concat "/"
match stripped with
| ListType child ->
let gone = go (prefix + "_") None child
match gone.Description with
| FieldDescription.NonRecursive ty ->
// Great, no recursion, just treat it as atomic
{
FieldName = name
ArgName =
match name with
| Some n -> Ident.lowerFirstLetter n
| None -> Ident.Create $"arg%s{prefix}"
Description = FieldDescription.NonRecursive stripped
}
| FieldDescription.Self ty ->
{
FieldName = name
ArgName =
match name with
| Some n -> Ident.lowerFirstLetter n
| None -> Ident.Create $"arg%s{prefix}"
Description = FieldDescription.ListSelf ty
}
| FieldDescription.ListSelf _ -> failwith "Deeply nested lists not currently supported"
| PrimitiveType _ ->
{
FieldName = name
ArgName =
match name with
| Some n -> Ident.lowerFirstLetter n
| None -> Ident.Create $"arg%s{prefix}"
Description = FieldDescription.NonRecursive stripped
}
| SynType.LongIdent (SynLongIdent.SynLongIdent (ty, _, _)) ->
let key = ty |> List.map _.idText |> String.concat "/"
let isKnownUnion = let isKnownUnion =
allUnionTypes |> List.exists (fun unionTy -> getNameKey unionTy = key) allUnionTypes |> List.exists (fun unionTy -> getNameKey unionTy = key)
@@ -339,7 +333,8 @@ module internal CataGenerator =
match name with match name with
| Some n -> Ident.lowerFirstLetter n | Some n -> Ident.lowerFirstLetter n
| None -> Ident.Create $"arg%s{prefix}" | None -> Ident.Create $"arg%s{prefix}"
Description = FieldDescription.Self stripped Description = FieldDescription.Self ty
RequiredGenerics = typeArgs
} }
else else
{ {
@@ -348,10 +343,81 @@ module internal CataGenerator =
match name with match name with
| Some n -> Ident.lowerFirstLetter n | Some n -> Ident.lowerFirstLetter n
| None -> Ident.Create $"arg%s{prefix}" | None -> Ident.Create $"arg%s{prefix}"
Description = FieldDescription.NonRecursive stripped Description = FieldDescription.NonRecursive ty
RequiredGenerics = typeArgs
} }
| _ -> failwithf "Unrecognised type: %+A" stripped let rec dealWithType (typeArgs : int list option) (stripped : SynType) =
match stripped with
| ListType child ->
let gone = go (prefix + "_") None child
match gone.Description with
| FieldDescription.NonRecursive ty ->
// Great, no recursion, just treat it as atomic
{
FieldName = name
ArgName =
match name with
| Some n -> Ident.lowerFirstLetter n
| None -> Ident.Create $"arg%s{prefix}"
Description = FieldDescription.NonRecursive stripped
RequiredGenerics = typeArgs
}
| FieldDescription.Self ty ->
{
FieldName = name
ArgName =
match name with
| Some n -> Ident.lowerFirstLetter n
| None -> Ident.Create $"arg%s{prefix}"
Description = FieldDescription.ListSelf ty
RequiredGenerics = typeArgs
}
| FieldDescription.ListSelf _ -> failwith "Deeply nested lists not currently supported"
| PrimitiveType _ ->
{
FieldName = name
ArgName =
match name with
| Some n -> Ident.lowerFirstLetter n
| None -> Ident.Create $"arg%s{prefix}"
Description = FieldDescription.NonRecursive stripped
RequiredGenerics = typeArgs
}
| SynType.App (ty, _, childTypeArgs, _, _, _, _) ->
match typeArgs with
| Some _ -> failwithf "Nested applications of types not supported in %+A" ty
| None ->
let childTypeArgs =
childTypeArgs
|> List.map (fun generic ->
let generic =
match generic with
| SynType.Var (SynTypar.SynTypar (name, _, _), _) -> name
| _ -> failwithf "Unrecognised generic arg: %+A" generic
availableGenerics
|> List.findIndex (fun knownGeneric -> knownGeneric.idText = generic.idText)
)
dealWithType (Some childTypeArgs) (SynType.stripOptionalParen ty)
| SynType.LongIdent (SynLongIdent.SynLongIdent (ty, _, _)) -> dealWithPrimitive typeArgs stripped ty
| SynType.Var (typar, _) ->
{
FieldName = name
ArgName =
match name with
| Some n -> Ident.lowerFirstLetter n
| None -> Ident.Create $"arg%s{prefix}"
Description = FieldDescription.NonRecursive ty
RequiredGenerics = typeArgs
}
| _ -> failwithf "Unrecognised type: %+A" stripped
let stripped = SynType.stripOptionalParen ty
dealWithType None stripped
fields |> List.mapi (fun i x -> go $"%i{argIndex}_%i{i}" x.Name x.Type) fields |> List.mapi (fun i x -> go $"%i{argIndex}_%i{i}" x.Name x.Type)
@@ -410,6 +476,8 @@ module internal CataGenerator =
{ {
Name = name |> Option.map Ident.lowerFirstLetter Name = name |> Option.map Ident.lowerFirstLetter
Type = ty Type = ty
// TODO this is definitely wrong
GenericsOfParent = []
} }
) )
@@ -432,7 +500,27 @@ module internal CataGenerator =
Fields = Fields =
{ {
Name = None Name = None
Type = SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent union.ParentTypeName) Type =
let name =
SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent union.ParentTypeName)
match union.Typars with
| [] -> name
| typars ->
let typars =
typars
|> List.map (fun (SynTyparDecl (_, typar)) -> SynType.Var (typar, range0))
SynType.App (
name,
Some range0,
typars,
List.replicate (typars.Length - 1) range0,
Some range0,
false,
range0
)
GenericsOfParent = union.Typars
} }
|> List.singleton |> List.singleton
} }
@@ -445,12 +533,28 @@ module internal CataGenerator =
/// Build the DU which defines the states our state machine can be in. /// Build the DU which defines the states our state machine can be in.
let createInstructionType (analysis : UnionAnalysis list) : SynTypeDefn = let createInstructionType (analysis : UnionAnalysis list) : SynTypeDefn =
let parentGenerics =
analysis
|> List.collect _.Typars
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, SynTypar.SynTypar (ident, _, _))) -> ident.idText)
|> List.distinct
|> List.map (fun i ->
SynTyparDecl.SynTyparDecl ([], SynTypar.SynTypar (Ident.Create i, TyparStaticReq.None, false))
)
// One union case for each union type, and then // One union case for each union type, and then
// a union case for each union case which contains a recursive reference. // a union case for each union case which contains a recursive reference.
let casesFromProcess : SynUnionCase list = let casesFromProcess : SynUnionCase list =
baseCases analysis baseCases analysis
|> List.map (fun unionCase -> |> List.map (fun unionCase ->
SynUnionCase.Create (unionCase.Name, unionCase.Fields |> List.map (fun f -> SynField.Create f.Type)) let fields =
unionCase.Fields
|> List.map (fun field ->
// TODO: adjust type parameters
SynField.Create field.Type
)
SynUnionCase.Create (unionCase.Name, fields)
) )
let casesFromCases = let casesFromCases =
@@ -461,10 +565,28 @@ module internal CataGenerator =
let cases = casesFromProcess @ casesFromCases let cases = casesFromProcess @ casesFromCases
let typars =
let count = analysis |> List.map (fun x -> List.length x.Typars) |> List.max
if analysis |> List.forall (fun x -> x.Typars.IsEmpty) then
None
else
let typars =
analysis
|> List.collect _.Typars
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, SynTypar.SynTypar (ident, _, _))) -> ident.idText)
|> List.distinct
|> List.map (fun i ->
SynTyparDecl.SynTyparDecl ([], SynTypar.SynTypar (Ident.Create i, TyparStaticReq.None, false))
)
Some (SynTyparDecls.PostfixList (typars, [], range0))
SynTypeDefn.SynTypeDefn ( SynTypeDefn.SynTypeDefn (
SynComponentInfo.SynComponentInfo ( SynComponentInfo.SynComponentInfo (
[ SynAttributeList.Create [ SynAttribute.RequireQualifiedAccess () ] ], [ SynAttributeList.Create [ SynAttribute.RequireQualifiedAccess () ] ],
None, typars,
[], [],
[ Ident.Create "Instruction" ], [ Ident.Create "Instruction" ],
PreXmlDoc.Empty, PreXmlDoc.Empty,
@@ -514,7 +636,7 @@ module internal CataGenerator =
let componentInfo = let componentInfo =
SynComponentInfo.SynComponentInfo ( SynComponentInfo.SynComponentInfo (
[], [],
Some (SynTyparDecls.PostfixList (orderedGenerics, [], range0)), Some (SynTyparDecls.PostfixList (analysis.Typars @ orderedGenerics, [], range0)),
[], [],
[ analysis.CataTypeName ], [ analysis.CataTypeName ],
// TODO: better docstring // TODO: better docstring
@@ -557,7 +679,26 @@ module internal CataGenerator =
[ SynType.Var (generics.[getNameKeyUnion ty], range0) ], [ SynType.Var (generics.[getNameKeyUnion ty], range0) ],
true true
) )
| FieldDescription.NonRecursive ty -> ty | FieldDescription.NonRecursive ty ->
match field.RequiredGenerics with
| None -> ty
| Some generics ->
let generics =
generics
|> List.map (fun i ->
let (SynTyparDecl.SynTyparDecl (_, typar)) = analysis.Typars.[i]
SynType.Var (typar, range0)
)
SynType.App (
ty,
Some range0,
generics,
List.replicate (generics.Length - 1) range0,
Some range0,
false,
range0
)
SynType.Fun ( SynType.Fun (
SynType.SignatureParameter ( SynType.SignatureParameter (
@@ -625,30 +766,36 @@ module internal CataGenerator =
/// Build a record which contains one of every cata type. /// Build a record which contains one of every cata type.
/// That is, define a type Cata<{'ret<U> for U in T}> /// That is, define a type Cata<{'ret<U> for U in T}>
/// with one member for each U, namely of type [U]Cata<{'ret<U> for U in T}>. /// with one member for each U, namely of type [U]Cata<{'ret<U> for U in T}>.
// TODO: this should take an analysis instead let createCataRecord (cataName : Ident) (doc : PreXmlDoc) (analysis : UnionAnalysis list) : SynTypeDefn =
let createCataRecord (cataName : Ident) (doc : PreXmlDoc) (allUnionTypes : SynTypeDefn list) : SynTypeDefn = // An artificial generic for each union type
let generics = let generics =
allUnionTypes analysis
|> List.map (fun defn -> |> List.map (fun analysis -> SynTypar.SynTypar (analysis.GenericName, TyparStaticReq.None, false))
let name = getName defn |> List.map _.idText |> String.concat "" |> Ident.Create
SynTypar.SynTypar (name, TyparStaticReq.None, false)
)
// A field for each cata
let fields = let fields =
allUnionTypes analysis
|> List.map (fun unionType -> |> List.map (fun analysis ->
let nameForDoc = List.last (getName unionType) |> _.idText let nameForDoc = List.last(analysis.ParentTypeName).idText
let doc = let doc =
PreXmlDoc.Create $" How to perform a fold (catamorphism) over the type %s{nameForDoc}" PreXmlDoc.Create $" How to perform a fold (catamorphism) over the type %s{nameForDoc}"
let name = getName unionType let artificialGenerics = generics |> List.map (fun v -> SynType.Var (v, range0))
let userInputGenerics =
analysis.Typars
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, SynTypar.SynTypar (ident, _, _))) -> ident.idText)
|> List.distinct
|> List.map (fun i ->
SynType.Var (SynTypar.SynTypar (Ident.Create i, TyparStaticReq.None, false), range0)
)
let ty = let ty =
SynType.App ( SynType.App (
SynType.CreateLongIdent (SynLongIdent.CreateString (List.last(name).idText + "CataCase")), SynType.LongIdent (SynLongIdent.CreateFromLongIdent [ analysis.CataTypeName ]),
Some range0, Some range0,
generics |> List.map (fun v -> SynType.Var (v, range0)), userInputGenerics @ artificialGenerics,
List.replicate (generics.Length - 1) range0, List.replicate (generics.Length - 1) range0,
Some range0, Some range0,
false, false,
@@ -658,7 +805,7 @@ module internal CataGenerator =
SynField.SynField ( SynField.SynField (
[], [],
false, false,
Some (List.last name), Some (List.last analysis.ParentTypeName),
ty, ty,
false, false,
doc, doc,
@@ -670,16 +817,23 @@ module internal CataGenerator =
) )
) )
// A "real" generic for each generic in the user-provided type
let genericsFromUserInput =
analysis
|> List.collect _.Typars
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, SynTypar.SynTypar (ident, _, _))) -> ident.idText)
|> List.distinct
|> List.map (fun i ->
SynTyparDecl.SynTyparDecl ([], SynTypar.SynTypar (Ident.Create i, TyparStaticReq.None, false))
)
let genericsFromCata =
generics |> List.map (fun ty -> SynTyparDecl.SynTyparDecl ([], ty))
let componentInfo = let componentInfo =
SynComponentInfo.SynComponentInfo ( SynComponentInfo.SynComponentInfo (
[], [],
Some ( Some (SynTyparDecls.PostfixList (genericsFromUserInput @ genericsFromCata, [], range0)),
SynTyparDecls.PostfixList (
generics |> List.map (fun ty -> SynTyparDecl.SynTyparDecl ([], ty)),
[],
range0
)
),
[], [],
[ cataName ], [ cataName ],
doc, doc,
@@ -714,8 +868,10 @@ module internal CataGenerator =
allUnionTypes allUnionTypes
|> List.map (fun unionType -> |> List.map (fun unionType ->
let cases, typars, access = AstHelper.getUnionCases unionType
let cases = let cases =
AstHelper.getUnionCases unionType cases
|> List.map (fun prod -> |> List.map (fun prod ->
let fields = let fields =
prod.Fields prod.Fields
@@ -723,14 +879,16 @@ module internal CataGenerator =
|> List.collect (fun (i, node) -> |> List.collect (fun (i, node) ->
match getNameUnion node.Type with match getNameUnion node.Type with
| None -> | None ->
analyse allRecordTypes allUnionTypes i [ node ] |> List.map CataUnionField.Basic analyse typars allRecordTypes allUnionTypes i [ node ]
|> List.map CataUnionField.Basic
| Some name -> | Some name ->
match Map.tryFind (List.last(name).idText) recordTypes with match Map.tryFind (List.last(name).idText) recordTypes with
| None -> | None ->
analyse allRecordTypes allUnionTypes i [ node ] |> List.map CataUnionField.Basic analyse typars allRecordTypes allUnionTypes i [ node ]
|> List.map CataUnionField.Basic
| Some fields -> | Some fields ->
List.zip fields (analyse allRecordTypes allUnionTypes i fields) List.zip fields (analyse typars allRecordTypes allUnionTypes i fields)
|> List.map (fun (field, analysis) -> Option.get field.Name, analysis) |> List.map (fun (field, analysis) -> Option.get field.Name, analysis)
|> CataUnionField.Record |> CataUnionField.Record
|> List.singleton |> List.singleton
@@ -742,6 +900,8 @@ module internal CataGenerator =
let unionTypeName = getName unionType let unionTypeName = getName unionType
{ {
Typars = typars
Accessibility = access
StackName = StackName =
List.last(getName unionType).idText + "Stack" List.last(getName unionType).idText + "Stack"
|> Ident.Create |> Ident.Create
@@ -1218,6 +1378,35 @@ module internal CataGenerator =
None None
) )
let userSuppliedGenerics =
analysis
|> List.collect _.Typars
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, SynTypar.SynTypar (ident, _, _))) -> ident.idText)
|> List.distinct
|> List.map (fun i -> SynTypar.SynTypar (Ident.Create i, TyparStaticReq.None, false))
let instructionsArrType =
if not userSuppliedGenerics.IsEmpty then
SynType.App (
SynType.CreateLongIdent "Instruction",
Some range0,
userSuppliedGenerics |> List.map (fun x -> SynType.Var (x, range0)),
List.replicate (userSuppliedGenerics.Length - 1) range0,
Some range0,
false,
range0
)
else
SynType.CreateLongIdent "Instruction"
let cataGenerics =
[
for generic in userSuppliedGenerics do
yield SynType.Var (generic, range0)
for case in analysis do
yield SynType.Var (SynTypar.SynTypar (case.GenericName, TyparStaticReq.None, false), range0)
]
let headPat = let headPat =
SynPat.LongIdent ( SynPat.LongIdent (
SynLongIdent.CreateString "loop", SynLongIdent.CreateString "loop",
@@ -1231,8 +1420,8 @@ module internal CataGenerator =
SynType.App ( SynType.App (
SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent [ cataTypeName ]), SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent [ cataTypeName ]),
Some range0, Some range0,
List.replicate analysis.Length (SynType.Anon range0), cataGenerics,
List.replicate (analysis.Length - 1) range0, List.replicate (cataGenerics.Length - 1) range0,
Some range0, Some range0,
false, false,
range0 range0
@@ -1245,7 +1434,7 @@ module internal CataGenerator =
SynType.App ( SynType.App (
SynType.CreateLongIdent "ResizeArray", SynType.CreateLongIdent "ResizeArray",
Some range0, Some range0,
[ SynType.CreateLongIdent "Instruction" ], [ instructionsArrType ],
[], [],
Some range0, Some range0,
false, false,
@@ -1347,7 +1536,20 @@ module internal CataGenerator =
SynPat.Named (SynIdent.SynIdent (unionCase.StackName, None), false, None, range0), SynPat.Named (SynIdent.SynIdent (unionCase.StackName, None), false, None, range0),
None, None,
SynExpr.CreateApp ( SynExpr.CreateApp (
SynExpr.CreateLongIdent (SynLongIdent.CreateString "ResizeArray"), SynExpr.TypeApp (
SynExpr.CreateIdent (Ident.Create "ResizeArray"),
range0,
[
SynType.Var (
SynTypar.SynTypar (unionCase.GenericName, TyparStaticReq.None, false),
range0
)
],
[],
Some range0,
range0,
range0
),
SynExpr.CreateConst SynConst.Unit SynExpr.CreateConst SynConst.Unit
), ),
range0, range0,
@@ -1404,6 +1606,9 @@ module internal CataGenerator =
xmldoc = PreXmlDoc.Create $" Methods to perform a catamorphism over the type %s{parentName}" xmldoc = PreXmlDoc.Create $" Methods to perform a catamorphism over the type %s{parentName}"
) )
let cataVarName = Ident.Create "cata"
let analysis = makeUnionAnalyses cataVarName allRecordTypes allUnionTypes
let allTypars = let allTypars =
allUnionTypes allUnionTypes
|> List.map (fun unionType -> |> List.map (fun unionType ->
@@ -1414,12 +1619,20 @@ module internal CataGenerator =
|> fun x -> SynType.Var (x, range0) |> fun x -> SynType.Var (x, range0)
) )
let runFunctions = let userProvidedGenerics =
List.zip allUnionTypes allTypars analysis
|> List.map (fun (unionType, relevantTypar) -> createRunFunction cataName allTypars relevantTypar unionType) |> List.collect _.Typars
|> List.map (fun (SynTyparDecl (_, SynTypar.SynTypar (ident, _, _))) -> ident.idText)
|> List.distinct
|> List.map (fun x ->
SynTyparDecl.SynTyparDecl ([], SynTypar.SynTypar (Ident.Create x, TyparStaticReq.None, false))
)
let cataVarName = Ident.Create "cata" let runFunctions =
let analysis = makeUnionAnalyses cataVarName allRecordTypes allUnionTypes List.zip analysis allTypars
|> List.map (fun (analysis, relevantTypar) ->
createRunFunction cataName userProvidedGenerics allTypars relevantTypar analysis
)
let cataStructures = let cataStructures =
createCataStructure analysis createCataStructure analysis
@@ -1432,7 +1645,7 @@ module internal CataGenerator =
$" Specifies how to perform a fold (catamorphism) over the type %s{parentName} and its friends." $" Specifies how to perform a fold (catamorphism) over the type %s{parentName} and its friends."
let cataRecord = let cataRecord =
SynModuleDecl.Types ([ createCataRecord cataName recordDoc allUnionTypes ], range0) SynModuleDecl.Types ([ createCataRecord cataName recordDoc analysis ], range0)
SynModuleOrNamespace.CreateNamespace ( SynModuleOrNamespace.CreateNamespace (
ns, ns,
@@ -1453,6 +1666,54 @@ module internal CataGenerator =
] ]
) )
let generate (context : GeneratorContext) : Output =
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
let types = Ast.extractTypeDefn ast
let opens = AstHelper.extractOpens ast
let namespaceAndTypes =
types
|> List.choose (fun (ns, types) ->
let typeWithAttr =
types
|> List.tryPick (fun ty ->
match Ast.getAttribute<CreateCatamorphismAttribute> ty with
| None -> None
| Some attr -> Some (attr.ArgExpr, ty)
)
match typeWithAttr with
| Some taggedType ->
let unions, records, others =
(([], [], []), types)
||> List.fold (fun
(unions, records, others)
(SynTypeDefn.SynTypeDefn (_, repr, _, _, _, _) as ty) ->
match repr with
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union _, _) ->
ty :: unions, records, others
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record _, _) ->
unions, ty :: records, others
| _ -> unions, records, ty :: others
)
if not others.IsEmpty then
failwith
$"Error: all types recursively defined together with a CreateCatamorphism type must be discriminated unions or records. %+A{others}"
Some (ns, taggedType, unions, records)
| _ -> None
)
let modules =
namespaceAndTypes
|> List.map (fun (ns, taggedType, unions, records) -> createModule opens ns taggedType unions records)
Output.Ast modules
/// Myriad generator that provides a catamorphism for an algebraic data type. /// Myriad generator that provides a catamorphism for an algebraic data type.
[<MyriadGenerator("create-catamorphism")>] [<MyriadGenerator("create-catamorphism")>]
type CreateCatamorphismGenerator () = type CreateCatamorphismGenerator () =
@@ -1460,52 +1721,4 @@ type CreateCatamorphismGenerator () =
interface IMyriadGenerator with interface IMyriadGenerator with
member _.ValidInputExtensions = [ ".fs" ] member _.ValidInputExtensions = [ ".fs" ]
member _.Generate (context : GeneratorContext) = member _.Generate (context : GeneratorContext) = CataGenerator.generate context
let ast, _ =
Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head
let types = Ast.extractTypeDefn ast
let opens = AstHelper.extractOpens ast
let namespaceAndTypes =
types
|> List.choose (fun (ns, types) ->
let typeWithAttr =
types
|> List.tryPick (fun ty ->
match Ast.getAttribute<CreateCatamorphismAttribute> ty with
| None -> None
| Some attr -> Some (attr.ArgExpr, ty)
)
match typeWithAttr with
| Some taggedType ->
let unions, records, others =
(([], [], []), types)
||> List.fold (fun
(unions, records, others)
(SynTypeDefn.SynTypeDefn (_, repr, _, _, _, _) as ty) ->
match repr with
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union _, _) ->
ty :: unions, records, others
| SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record _, _) ->
unions, ty :: records, others
| _ -> unions, records, ty :: others
)
if not others.IsEmpty then
failwith
$"Error: all types recursively defined together with a CreateCatamorphism type must be discriminated unions or records. %+A{others}"
Some (ns, taggedType, unions, records)
| _ -> None
)
let modules =
namespaceAndTypes
|> List.map (fun (ns, taggedType, unions, records) ->
CataGenerator.createModule opens ns taggedType unions records
)
Output.Ast modules