From 7b495050648ae6fa1f23c8183590e20a2e78ea02 Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Mon, 19 Feb 2024 00:57:14 +0000 Subject: [PATCH] Absolute bare-bones support for generics in cata (#101) --- ConsumePlugin/Catamorphism.fs | 20 +- ConsumePlugin/ConsumePlugin.fsproj | 4 + ConsumePlugin/FSharpForFunAndProfitCata.fs | 16 - ConsumePlugin/GeneratedCatamorphism.fs | 34 +- ConsumePlugin/GeneratedFileSystem.fs | 117 +--- ConsumePlugin/List.fs | 19 + ConsumePlugin/ListCata.fs | 118 ++++ README.md | 12 +- .../TestCataGenerator/TestCataGenerator.fs | 12 +- .../TestCataGenerator/TestMyList.fs | 30 +- .../TestCataGenerator/TestMyList2.fs | 9 +- WoofWare.Myriad.Plugins/AstHelper.fs | 82 ++- WoofWare.Myriad.Plugins/CataGenerator.fs | 547 ++++++++++++------ 13 files changed, 648 insertions(+), 372 deletions(-) create mode 100644 ConsumePlugin/List.fs create mode 100644 ConsumePlugin/ListCata.fs diff --git a/ConsumePlugin/Catamorphism.fs b/ConsumePlugin/Catamorphism.fs index ab271e1..04cd1f0 100644 --- a/ConsumePlugin/Catamorphism.fs +++ b/ConsumePlugin/Catamorphism.fs @@ -2,8 +2,8 @@ namespace ConsumePlugin open WoofWare.Myriad.Plugins -type Const = - | Int of int +type Const<'a> = + | Verbatim of 'a | String of string type PairOpKind = @@ -11,12 +11,12 @@ type PairOpKind = | ThenDoSeq [] -type Tree = - | Const of Const - | Pair of Tree * Tree * PairOpKind - | Sequential of Tree list - | Builder of Tree * TreeBuilder +type Tree<'a, 'b> = + | Const of Const<'a> * 'b + | Pair of Tree<'a, 'b> * Tree<'a, 'b> * PairOpKind + | Sequential of Tree<'a, 'b> list + | Builder of Tree<'a, 'b> * TreeBuilder<'b, 'a> -and TreeBuilder = - | Child of TreeBuilder - | Parent of Tree +and TreeBuilder<'b, 'a> = + | Child of TreeBuilder<'b, 'a> + | Parent of Tree<'a, 'b> diff --git a/ConsumePlugin/ConsumePlugin.fsproj b/ConsumePlugin/ConsumePlugin.fsproj index 67dc38a..31056bc 100644 --- a/ConsumePlugin/ConsumePlugin.fsproj +++ b/ConsumePlugin/ConsumePlugin.fsproj @@ -47,6 +47,10 @@ FSharpForFunAndProfitCata.fs + + + List.fs + diff --git a/ConsumePlugin/FSharpForFunAndProfitCata.fs b/ConsumePlugin/FSharpForFunAndProfitCata.fs index 121e66e..e8bf751 100644 --- a/ConsumePlugin/FSharpForFunAndProfitCata.fs +++ b/ConsumePlugin/FSharpForFunAndProfitCata.fs @@ -50,19 +50,3 @@ type Gift = | Wrapped of Gift * WrappingPaperStyle | Boxed of Gift | WithACard of Gift * message : string - -[] -type MyList = - | Nil - | Cons of ConsCase - -and ConsCase = - { - Head : int - Tail : MyList - } - -[] -type MyList2 = - | Nil - | Cons of int * MyList2 diff --git a/ConsumePlugin/GeneratedCatamorphism.fs b/ConsumePlugin/GeneratedCatamorphism.fs index 369e1c6..192a327 100644 --- a/ConsumePlugin/GeneratedCatamorphism.fs +++ b/ConsumePlugin/GeneratedCatamorphism.fs @@ -12,16 +12,16 @@ namespace ConsumePlugin open WoofWare.Myriad.Plugins /// 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 abstract Child : 'TreeBuilder -> 'TreeBuilder /// How to operate on the Parent case abstract Parent : 'Tree -> 'TreeBuilder /// 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 - abstract Const : Const -> 'Tree + abstract Const : Const<'a> -> 'b -> 'Tree /// How to operate on the Pair case abstract Pair : 'Tree -> 'Tree -> PairOpKind -> 'Tree /// How to operate on the Sequential case @@ -30,30 +30,30 @@ type TreeCataCase<'TreeBuilder, 'Tree> = abstract Builder : 'Tree -> 'TreeBuilder -> 'Tree /// 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 - TreeBuilder : TreeBuilderCataCase<'TreeBuilder, 'Tree> + TreeBuilder : TreeBuilderCataCase<'b, 'a, 'TreeBuilder, '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 [] module TreeCata = [] - type private Instruction = - | Process__TreeBuilder of TreeBuilder - | Process__Tree of Tree + type private Instruction<'b, 'a> = + | Process__TreeBuilder of TreeBuilder<'b, 'a> + | Process__Tree of Tree<'a, 'b> | TreeBuilder_Child | TreeBuilder_Parent | Tree_Pair of PairOpKind | Tree_Sequential of int | Tree_Builder - let private loop (cata : TreeCata<_, _>) (instructions : ResizeArray) = - let treeStack = ResizeArray () - let treeBuilderStack = ResizeArray () + let private loop (cata : TreeCata<'b, 'a, 'TreeBuilder, 'Tree>) (instructions : ResizeArray>) = + let treeStack = ResizeArray<'Tree> () + let treeBuilderStack = ResizeArray<'TreeBuilder> () while instructions.Count > 0 do let currentInstruction = instructions.[instructions.Count - 1] @@ -70,7 +70,7 @@ module TreeCata = instructions.Add (Instruction.Process__Tree arg0_0) | Instruction.Process__Tree x -> 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) -> instructions.Add (Instruction.Tree_Pair (arg2_0)) instructions.Add (Instruction.Process__Tree arg0_0) @@ -120,14 +120,18 @@ module TreeCata = treeBuilderStack, treeStack /// 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 () instructions.Add (Instruction.Process__TreeBuilder x) let treeBuilderRetStack, treeRetStack = loop cata instructions Seq.exactlyOne treeBuilderRetStack /// 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 () instructions.Add (Instruction.Process__Tree x) let treeBuilderRetStack, treeRetStack = loop cata instructions diff --git a/ConsumePlugin/GeneratedFileSystem.fs b/ConsumePlugin/GeneratedFileSystem.fs index a35c45d..4021d70 100644 --- a/ConsumePlugin/GeneratedFileSystem.fs +++ b/ConsumePlugin/GeneratedFileSystem.fs @@ -33,8 +33,8 @@ module FileSystemItemCata = | Process__FileSystemItem of FileSystemItem | FileSystemItem_Directory of string * int * int - let private loop (cata : FileSystemCata<_>) (instructions : ResizeArray) = - let fileSystemItemStack = ResizeArray () + let private loop (cata : FileSystemCata<'FileSystemItem>) (instructions : ResizeArray) = + let fileSystemItemStack = ResizeArray<'FileSystemItem> () while instructions.Count > 0 do let currentInstruction = instructions.[instructions.Count - 1] @@ -108,8 +108,8 @@ module GiftCata = | Gift_Boxed | Gift_WithACard of string - let private loop (cata : GiftCata<_>) (instructions : ResizeArray) = - let giftStack = ResizeArray () + let private loop (cata : GiftCata<'Gift>) (instructions : ResizeArray) = + let giftStack = ResizeArray<'Gift> () while instructions.Count > 0 do let currentInstruction = instructions.[instructions.Count - 1] @@ -150,112 +150,3 @@ module GiftCata = instructions.Add (Instruction.Process__Gift x) let giftRetStack = loop cata instructions 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 -[] -module MyListCata = - [] - type private Instruction = - | Process__MyList of MyList - | MyList_Cons of int - - let private loop (cata : MyListCata<_>) (instructions : ResizeArray) = - 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 -[] -module MyList2Cata = - [] - type private Instruction = - | Process__MyList2 of MyList2 - | MyList2_Cons of int - - let private loop (cata : MyList2Cata<_>) (instructions : ResizeArray) = - 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 diff --git a/ConsumePlugin/List.fs b/ConsumePlugin/List.fs new file mode 100644 index 0000000..32b1601 --- /dev/null +++ b/ConsumePlugin/List.fs @@ -0,0 +1,19 @@ +namespace ConsumePlugin + +open WoofWare.Myriad.Plugins + +[] +type MyList<'a> = + | Nil + | Cons of ConsCase<'a> + +and ConsCase<'a> = + { + Head : 'a + Tail : MyList<'a> + } + +[] +type MyList2<'a> = + | Nil + | Cons of 'a * MyList2<'a> diff --git a/ConsumePlugin/ListCata.fs b/ConsumePlugin/ListCata.fs new file mode 100644 index 0000000..5c75d8e --- /dev/null +++ b/ConsumePlugin/ListCata.fs @@ -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 +[] +module MyListCata = + [] + type private Instruction<'a> = + | Process__MyList of MyList<'a> + | MyList_Cons of 'a + + let private loop (cata : MyListCata<'a, 'MyList>) (instructions : ResizeArray>) = + 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 +[] +module MyList2Cata = + [] + type private Instruction<'a> = + | Process__MyList2 of MyList2<'a> + | MyList2_Cons of 'a + + let private loop (cata : MyList2Cata<'a, 'MyList2>) (instructions : ResizeArray>) = + 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 diff --git a/README.md b/README.md index e598b47..f6845c8 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,7 @@ thereby allowing the programmer to use F#'s record-update syntax. Takes a collection of mutually recursive discriminated unions: ```fsharp -[] +[] type Expr = | Const of Const | Pair of Expr * Expr * PairOpKind @@ -356,7 +356,7 @@ type ExprBuilderCata<'Expr, 'ExprBuilder> = abstract Child : 'ExprBuilder -> 'ExprBuilder abstract Parent : 'Expr -> 'ExprBuilder -type Cata<'Expr, 'ExprBuilder> = +type MyCata<'Expr, 'ExprBuilder> = { Expr : ExprCata<'Expr, 'ExprBuilder> ExprBuilder : ExprBuilderCata<'Expr, 'ExprBuilder> @@ -364,10 +364,10 @@ type Cata<'Expr, 'ExprBuilder> = [] module ExprCata = - let runExpr (cata : Cata<'ExprRet, 'ExprBuilderRet>) (x : Expr) : 'ExprRet = + let runExpr (cata : MyCata<'ExprRet, 'ExprBuilderRet>) (x : Expr) : 'ExprRet = 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" ``` @@ -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). 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 `[]` attribute. * 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 diff --git a/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestCataGenerator.fs b/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestCataGenerator.fs index 8516b68..1d53a6e 100644 --- a/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestCataGenerator.fs +++ b/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestCataGenerator.fs @@ -8,17 +8,17 @@ open FsCheck [] module TestCataGenerator = - let idCata : TreeCata<_, _> = + let idCata<'a, 'b> : TreeCata<'a, 'b, _, _> = { Tree = - { new TreeCataCase<_, _> with - member _.Const x = Const x + { new TreeCataCase<_, _, _, _> with + member _.Const x y = Const (x, y) member _.Pair x y z = Pair (x, y, z) member _.Sequential xs = Sequential xs member _.Builder x b = Builder (x, b) } TreeBuilder = - { new TreeBuilderCataCase<_, _> with + { new TreeBuilderCataCase<_, _, _, _> with member _.Child x = Child x member _.Parent x = Parent x } @@ -27,7 +27,7 @@ module TestCataGenerator = [] let ``Example`` () = 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 @@ -36,7 +36,7 @@ module TestCataGenerator = let ``Cata works`` () = let builderCases = ref 0 - let property (x : Tree) = + let property (x : Tree) = match x with | Tree.Builder _ -> Interlocked.Increment builderCases |> ignore | _ -> () diff --git a/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList.fs b/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList.fs index ddc891c..f39ef0a 100644 --- a/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList.fs +++ b/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList.fs @@ -8,10 +8,10 @@ open ConsumePlugin [] module TestMyList = - let idCata : MyListCata<_> = + let idCata<'a> : MyListCata<'a, _> = { MyList = - { new MyListCataCase<_> with + { new MyListCataCase<'a, _> with member _.Nil = MyList.Nil member _.Cons head tail = @@ -21,36 +21,32 @@ module TestMyList = Tail = tail } } - } [] let ``Cata works`` () = - let property (x : MyList) = MyListCata.runMyList idCata x = x + let property (x : MyList) = MyListCata.runMyList idCata x = x Check.QuickThrowOnFailure property - let toListCata = + let toListCata<'a> = { MyList = - { new MyListCataCase with + { new MyListCataCase<'a, 'a list> with 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 [] let ``Example of a fold converting to a new data structure`` () = - let rec toListNaive (l : MyList) : int list = + let rec toListNaive (l : MyList) : int list = match l with | MyList.Nil -> [] - | MyList.Cons { - Head = head - Tail = tail - } -> head :: toListNaive tail + | MyList.Cons consCell -> consCell.Head :: toListNaive consCell.Tail Check.QuickThrowOnFailure (fun l -> toListNaive l = toListViaCata l) @@ -62,20 +58,20 @@ module TestMyList = let sumCata = { MyList = - { new MyListCataCase with + { new MyListCataCase with member _.Nil = baseCase member _.Cons (head : int) (tail : int64) = atLeaf head tail } } - let viaCata (l : MyList) : int64 = MyListCata.runMyList sumCata l + let viaCata (l : MyList) : int64 = MyListCata.runMyList sumCata l - let viaFold (l : MyList) : int64 = + let viaFold (l : MyList) : int64 = // choose your favourite "to list" method - here I use the cata // but that could have been done naively (toListViaCata l, baseCase) ||> List.foldBack (fun elt state -> atLeaf elt state) - let property (l : MyList) = viaCata l = viaFold l + let property (l : MyList) = viaCata l = viaFold l Check.QuickThrowOnFailure property diff --git a/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList2.fs b/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList2.fs index e7f94db..1660b5b 100644 --- a/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList2.fs +++ b/WoofWare.Myriad.Plugins.Test/TestCataGenerator/TestMyList2.fs @@ -8,19 +8,18 @@ open ConsumePlugin [] module TestMyList2 = - let idCata : MyList2Cata<_> = + let idCata<'a> : MyList2Cata<'a, _> = { MyList2 = - { new MyList2CataCase<_> with + { new MyList2CataCase<'a, _> with member _.Nil = MyList2.Nil - member _.Cons head tail = MyList2.Cons (head, tail) + member _.Cons (head : 'a) (tail : MyList2<'a>) = MyList2.Cons (head, tail) } - } [] let ``Cata works`` () = - let property (x : MyList2) = MyList2Cata.runMyList2 idCata x = x + let property (x : MyList2) = MyList2Cata.runMyList2 idCata x = x Check.QuickThrowOnFailure property diff --git a/WoofWare.Myriad.Plugins/AstHelper.fs b/WoofWare.Myriad.Plugins/AstHelper.fs index 70f4138..21ad941 100644 --- a/WoofWare.Myriad.Plugins/AstHelper.fs +++ b/WoofWare.Myriad.Plugins/AstHelper.fs @@ -76,6 +76,9 @@ type internal AdtNode = { Type : SynType 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`); @@ -85,6 +88,10 @@ type internal AdtProduct = { Name : SynIdent 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 } [] @@ -400,29 +407,65 @@ module internal AstHelper = 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 | SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Union (_, cases, _), _) -> - cases - |> List.map (fun (SynUnionCase.SynUnionCase (_, ident, kind, _, _, _, _)) -> - match kind with - | SynUnionCaseKind.FullType _ -> failwith "FullType union cases not supported" - | SynUnionCaseKind.Fields fields -> - { - Name = ident - Fields = - fields - |> List.map (fun (SynField.SynField (_, _, id, ty, _, _, _, _, _)) -> - { - Type = ty - Name = id - } - ) - } - ) + let cases = + cases + |> List.map (fun (SynUnionCase.SynUnionCase (_, ident, kind, _, _, _, _)) -> + match kind with + | SynUnionCaseKind.FullType _ -> failwith "FullType union cases not supported" + | SynUnionCaseKind.Fields fields -> + { + Name = ident + Fields = + fields + |> List.map (fun (SynField.SynField (_, _, id, ty, _, _, _, _, _)) -> + { + Type = ty + Name = id + GenericsOfParent = typars + } + ) + Generics = typars + } + ) + + cases, typars, access | _ -> 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 | SynTypeDefnRepr.Simple (SynTypeDefnSimpleRepr.Record (_, fields, _), _) -> fields @@ -430,6 +473,7 @@ module internal AstHelper = { Name = ident Type = ty + GenericsOfParent = typars } ) | _ -> failwithf "Failed to get record elements for type that was: %+A" repr diff --git a/WoofWare.Myriad.Plugins/CataGenerator.fs b/WoofWare.Myriad.Plugins/CataGenerator.fs index ac5df31..b19f653 100644 --- a/WoofWare.Myriad.Plugins/CataGenerator.fs +++ b/WoofWare.Myriad.Plugins/CataGenerator.fs @@ -35,6 +35,10 @@ module internal CataGenerator = /// The relationship this field has with the parent type (or the /// recursive knot of parent types) 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 @@ -81,6 +85,8 @@ module internal CataGenerator = /// recursive knot), this is everything we need to know about it for the cata. type UnionAnalysis = { + Accessibility : SynAccess option + Typars : SynTyparDecl list /// The name of the stack we'll use for the results /// of returning from a descent into this union type, /// when performing the cata @@ -112,28 +118,70 @@ module internal CataGenerator = /// Seq.exactlyOne {relevantTypar}Stack let createRunFunction (cataName : Ident) - (allTypars : SynType list) + (userProvidedTypars : SynTyparDecl list) + (allArtificialTypars : SynType list) (relevantTypar : SynType) - (unionType : SynTypeDefn) + (analysis : UnionAnalysis) : SynBinding = - let relevantTypeName = - match unionType with - | SynTypeDefn.SynTypeDefn (SynComponentInfo.SynComponentInfo (longId = id), _, _, _, _, _) -> List.last id + let relevantTypeName = analysis.ParentTypeName - let allTyparNames = - allTypars + let allArtificialTyparNames = + allArtificialTypars |> List.map (fun ty -> match ty with | SynType.Var (SynTypar.SynTypar (ident = ident), _) -> ident | _ -> 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 = match relevantTypar with | SynType.Var (SynTypar.SynTypar (ident = ident), _) -> ident | _ -> 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 ( None, SynBindingKind.Normal, @@ -150,29 +198,8 @@ module internal CataGenerator = None ), SynPat.CreateLongIdent ( - SynLongIdent.CreateString ("run" + relevantTypeName.idText), - [ - 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 ]) - ) - ) - ] + SynLongIdent.CreateString ("run" + List.last(relevantTypeName).idText), + [ SynPat.CreateParen cataObject ; SynPat.CreateParen inputObject ] ), Some (SynBindingReturnInfo.Create relevantTypar), SynExpr.CreateTyped ( @@ -196,10 +223,7 @@ module internal CataGenerator = SynExpr.CreateLongIdent (SynLongIdent.Create [ "instructions" ; "Add" ]), SynExpr.CreateParen ( SynExpr.CreateApp ( - SynExpr.CreateLongIdent ( - SynLongIdent.Create - [ "Instruction" ; "Process__" + relevantTypeName.idText ] - ), + SynExpr.CreateLongIdent analysis.AssociatedProcessInstruction, SynExpr.CreateLongIdent (SynLongIdent.CreateString "x") ) ) @@ -219,8 +243,8 @@ module internal CataGenerator = Ident.Create (t.idText + "Stack") |> Ident.lowerFirstLetter ) ) - allTyparNames, - List.replicate (allTypars.Length - 1) range0, + allArtificialTyparNames, + List.replicate (allArtificialTyparNames.Length - 1) range0, range0 ), expr = @@ -262,9 +286,10 @@ module internal CataGenerator = match ty with | SynTypeDefn.SynTypeDefn (SynComponentInfo.SynComponentInfo (_, _, _, id, _, _, _, _), _, _, _, _, _) -> id - let getNameUnion (unionType : SynType) : LongIdent option = + let rec getNameUnion (unionType : SynType) : LongIdent option = match unionType with | SynType.LongIdent (SynLongIdent.SynLongIdent (name, _, _)) -> Some name + | SynType.App (ty, _, _, _, _, _, _) -> getNameUnion ty | _ -> None 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 /// recursive knot of user-provided DUs for which we are creating a cata. let analyse + (availableGenerics : SynTyparDecl list) (allRecordTypes : SynTypeDefn list) (allUnionTypes : SynTypeDefn list) (argIndex : int) (fields : AdtNode 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 stripped = SynType.stripOptionalParen ty - - 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 dealWithPrimitive (typeArgs : int list option) (ty : SynType) (typeName : LongIdent) = + let key = typeName |> List.map _.idText |> String.concat "/" let isKnownUnion = allUnionTypes |> List.exists (fun unionTy -> getNameKey unionTy = key) @@ -339,7 +333,8 @@ module internal CataGenerator = match name with | Some n -> Ident.lowerFirstLetter n | None -> Ident.Create $"arg%s{prefix}" - Description = FieldDescription.Self stripped + Description = FieldDescription.Self ty + RequiredGenerics = typeArgs } else { @@ -348,10 +343,81 @@ module internal CataGenerator = match name with | Some n -> Ident.lowerFirstLetter n | 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) @@ -410,6 +476,8 @@ module internal CataGenerator = { Name = name |> Option.map Ident.lowerFirstLetter Type = ty + // TODO this is definitely wrong + GenericsOfParent = [] } ) @@ -432,7 +500,27 @@ module internal CataGenerator = Fields = { 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 } @@ -445,12 +533,28 @@ module internal CataGenerator = /// Build the DU which defines the states our state machine can be in. 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 // a union case for each union case which contains a recursive reference. let casesFromProcess : SynUnionCase list = baseCases analysis |> 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 = @@ -461,10 +565,28 @@ module internal CataGenerator = 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 ( SynComponentInfo.SynComponentInfo ( [ SynAttributeList.Create [ SynAttribute.RequireQualifiedAccess () ] ], - None, + typars, [], [ Ident.Create "Instruction" ], PreXmlDoc.Empty, @@ -514,7 +636,7 @@ module internal CataGenerator = let componentInfo = SynComponentInfo.SynComponentInfo ( [], - Some (SynTyparDecls.PostfixList (orderedGenerics, [], range0)), + Some (SynTyparDecls.PostfixList (analysis.Typars @ orderedGenerics, [], range0)), [], [ analysis.CataTypeName ], // TODO: better docstring @@ -557,7 +679,26 @@ module internal CataGenerator = [ SynType.Var (generics.[getNameKeyUnion ty], range0) ], 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.SignatureParameter ( @@ -625,30 +766,36 @@ module internal CataGenerator = /// Build a record which contains one of every cata type. /// That is, define a type Cata<{'ret for U in T}> /// with one member for each U, namely of type [U]Cata<{'ret for U in T}>. - // TODO: this should take an analysis instead - let createCataRecord (cataName : Ident) (doc : PreXmlDoc) (allUnionTypes : SynTypeDefn list) : SynTypeDefn = + let createCataRecord (cataName : Ident) (doc : PreXmlDoc) (analysis : UnionAnalysis list) : SynTypeDefn = + // An artificial generic for each union type let generics = - allUnionTypes - |> List.map (fun defn -> - let name = getName defn |> List.map _.idText |> String.concat "" |> Ident.Create - SynTypar.SynTypar (name, TyparStaticReq.None, false) - ) + analysis + |> List.map (fun analysis -> SynTypar.SynTypar (analysis.GenericName, TyparStaticReq.None, false)) + // A field for each cata let fields = - allUnionTypes - |> List.map (fun unionType -> - let nameForDoc = List.last (getName unionType) |> _.idText + analysis + |> List.map (fun analysis -> + let nameForDoc = List.last(analysis.ParentTypeName).idText let doc = 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 = SynType.App ( - SynType.CreateLongIdent (SynLongIdent.CreateString (List.last(name).idText + "CataCase")), + SynType.LongIdent (SynLongIdent.CreateFromLongIdent [ analysis.CataTypeName ]), Some range0, - generics |> List.map (fun v -> SynType.Var (v, range0)), + userInputGenerics @ artificialGenerics, List.replicate (generics.Length - 1) range0, Some range0, false, @@ -658,7 +805,7 @@ module internal CataGenerator = SynField.SynField ( [], false, - Some (List.last name), + Some (List.last analysis.ParentTypeName), ty, false, 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 = SynComponentInfo.SynComponentInfo ( [], - Some ( - SynTyparDecls.PostfixList ( - generics |> List.map (fun ty -> SynTyparDecl.SynTyparDecl ([], ty)), - [], - range0 - ) - ), + Some (SynTyparDecls.PostfixList (genericsFromUserInput @ genericsFromCata, [], range0)), [], [ cataName ], doc, @@ -714,8 +868,10 @@ module internal CataGenerator = allUnionTypes |> List.map (fun unionType -> + let cases, typars, access = AstHelper.getUnionCases unionType + let cases = - AstHelper.getUnionCases unionType + cases |> List.map (fun prod -> let fields = prod.Fields @@ -723,14 +879,16 @@ module internal CataGenerator = |> List.collect (fun (i, node) -> match getNameUnion node.Type with | None -> - analyse allRecordTypes allUnionTypes i [ node ] |> List.map CataUnionField.Basic + analyse typars allRecordTypes allUnionTypes i [ node ] + |> List.map CataUnionField.Basic | Some name -> match Map.tryFind (List.last(name).idText) recordTypes with | None -> - analyse allRecordTypes allUnionTypes i [ node ] |> List.map CataUnionField.Basic + analyse typars allRecordTypes allUnionTypes i [ node ] + |> List.map CataUnionField.Basic | 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) |> CataUnionField.Record |> List.singleton @@ -742,6 +900,8 @@ module internal CataGenerator = let unionTypeName = getName unionType { + Typars = typars + Accessibility = access StackName = List.last(getName unionType).idText + "Stack" |> Ident.Create @@ -1218,6 +1378,35 @@ module internal CataGenerator = 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 = SynPat.LongIdent ( SynLongIdent.CreateString "loop", @@ -1231,8 +1420,8 @@ module internal CataGenerator = SynType.App ( SynType.CreateLongIdent (SynLongIdent.CreateFromLongIdent [ cataTypeName ]), Some range0, - List.replicate analysis.Length (SynType.Anon range0), - List.replicate (analysis.Length - 1) range0, + cataGenerics, + List.replicate (cataGenerics.Length - 1) range0, Some range0, false, range0 @@ -1245,7 +1434,7 @@ module internal CataGenerator = SynType.App ( SynType.CreateLongIdent "ResizeArray", Some range0, - [ SynType.CreateLongIdent "Instruction" ], + [ instructionsArrType ], [], Some range0, false, @@ -1347,7 +1536,20 @@ module internal CataGenerator = SynPat.Named (SynIdent.SynIdent (unionCase.StackName, None), false, None, range0), None, 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 ), range0, @@ -1404,6 +1606,9 @@ module internal CataGenerator = 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 = allUnionTypes |> List.map (fun unionType -> @@ -1414,12 +1619,20 @@ module internal CataGenerator = |> fun x -> SynType.Var (x, range0) ) - let runFunctions = - List.zip allUnionTypes allTypars - |> List.map (fun (unionType, relevantTypar) -> createRunFunction cataName allTypars relevantTypar unionType) + let userProvidedGenerics = + analysis + |> 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 analysis = makeUnionAnalyses cataVarName allRecordTypes allUnionTypes + let runFunctions = + List.zip analysis allTypars + |> List.map (fun (analysis, relevantTypar) -> + createRunFunction cataName userProvidedGenerics allTypars relevantTypar analysis + ) let cataStructures = createCataStructure analysis @@ -1432,7 +1645,7 @@ module internal CataGenerator = $" Specifies how to perform a fold (catamorphism) over the type %s{parentName} and its friends." let cataRecord = - SynModuleDecl.Types ([ createCataRecord cataName recordDoc allUnionTypes ], range0) + SynModuleDecl.Types ([ createCataRecord cataName recordDoc analysis ], range0) SynModuleOrNamespace.CreateNamespace ( 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 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. [] type CreateCatamorphismGenerator () = @@ -1460,52 +1721,4 @@ type CreateCatamorphismGenerator () = interface IMyriadGenerator with member _.ValidInputExtensions = [ ".fs" ] - member _.Generate (context : GeneratorContext) = - 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 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 + member _.Generate (context : GeneratorContext) = CataGenerator.generate context