[RFC FS-1023 Discussion] - Allow type providers to generate types from types #125
Replies: 64 comments 2 replies
-
Not sure this has been discussed before. type User =
{ Id: Guid
FirstName: string
LastName: string
Preference: Preference
Email: string
Friends: User [] }
// Post Request, Id, Preference, Friends fields are omitted
type NewUser = Omit<User, "Id|Preference|Friends">
type UserSummary = Omit<User, "Preference">
// type safe automapper?
type mapper = CreateMapper<UserDto, DbUserDto> This is borrowed from typescript but using TypeProvider Also would enable making constrained type easier. type PositiveInt = Constrained<int, <<@fun i -> i >= 0@>>
// generates the following code
module rec PositiveInt =
let validate = fun i -> i >= 0
let create str =
if validate str
then Some <| unsafeCreate str
else None
let unsafeCreate str = PositiveInt str
let inner = function PositiveInt x -> x
[<Struct>]
type T = private PositiveInt of int
type PositiveInt = PositiveInt.T And finally who doesn't want code first like database ORM/schema management, without some sort of IDE plugins? type UserTable =
{ Id: Guid
UserName: String
Email: String option
Password: String }
and TodoTable =
{ Id: Guid
Msg: String
UserId: Guid }
// Food for thought only
type DB = PostgresOrm<UserTable [] * TodoTable[], config> |
Beta Was this translation helpful? Give feedback.
-
Erasing type providers only would severely limit the potential, but it seems maybe a good target as a first step forward. It seems to me that they could still be used to generate lenses or serialization but not more complex things like type proxies or future reflectable artifacts. Maybe for the sake of moving this forward it would be best to limit the scope as much as possible and allow more complex use cases later. |
Beta Was this translation helpful? Give feedback.
-
type UC = {a:int; b:string}
let QT<'a> = <@ typeof<'a> @>
type PT = MyTP<QT<UC>> |
Beta Was this translation helpful? Give feedback.
-
I think all the use cases above can be divided into two categories:
The first category isn't worth to implement as you can do exactly the same stuff passing, say, JSON or XML or some sort of micro DSL to a TP, which may be more readable, than a F# record. As it's compile time checked (it is), I see little difference between this and passing a one-shot-use record. What do you guys think about implementing fsharp/fslang-suggestions#450 instead? However, it would result with ugly code like type TP = MyTP<<@ typeof<T> @>> I.e. you cannot pass other type easily, like Honesty, all these attempts to use TPs in cases where macros should be used result with restricted solutions and clunky client code. Considering how much time it would certainly take to implement, I'm not sure anymore it's worth it. Real macros should have access to everything available on a certain compilation stage, like current method, type, module, namespace, project. And it should be able to amend some parts of the typed AST, like adding/removing members, types, attributes and the like. I think the best thing we can do to add real metaprogramming into F# is learning how macros are implemented in, say, Nemerle, refine the idea, then make a plan to create something similar in F#. This is certainly deserve a lot of time and resources to implement. (somewhat) better TPs? I'm not sure. |
Beta Was this translation helpful? Give feedback.
-
(if the semantics are wrong, just imagine the closest thing that would be right 😜 ) CmdletProvider<...>The first example I can think of involves Argu and posh (powershell). This is a basic implementation of CLI args as a DU with Argu that gets you an argument parser with relative ease. // CLI argument DUs
type AddReferenceArgs =
| [<CLIAlt "-n">] Name of string
| [<CLIAlt "-p">] Project of string
interface IArgParserTemplate with
member this.Usage =
match this with
| Name _-> "Reference name"
| Project _ -> "Project to which reference will be added"
type AddProjectArgs =
| [<CLIAlt "-n">] Name of string
| [<CLIAlt "-p">] Project of string
interface IArgParserTemplate with
member this.Usage =
match this with
| Name _-> "Project reference path"
| Project _ -> "Project to which reference will be added"
// command handlers
let addReference cont (results : ParseResults<AddReferenceArgs>) =
maybe {
let! name = results.TryGetResult <@ AddReferenceArgs.Name @>
let! project = results.TryGetResult <@ AddReferenceArgs.Project @>
Furnace.loadFsProject project
|> Furnace.addReference (name, None, None, None, None, None)
|> ignore
return cont
}
let addProject cont (results : ParseResults<AddProjectArgs>) =
maybe {
let! path = results.TryGetResult <@ AddProjectArgs.Name @>
let! project = results.TryGetResult <@ AddProjectArgs.Project @>
let name = Path.GetFileName path
let newProject = Furnace.loadFsProject path
Furnace.loadFsProject project
|> Furnace.addProjectReference(path, Some name, None, newProject.ProjectData.Settings.ProjectGuid.Data, None)
|> ignore
return cont
} These CLI arg types and the handlers that process the argument values could be fed into a This is what an F# type that compiles into a Cmdlet looks like : [<Cmdlet("Search", "File")>]
type SearchFileCmdlet() =
inherit PSCmdlet()
/// Regex pattern used in the search.
[<Parameter(Mandatory = true, Position = 0)>]
[<ValidateNotNullOrEmpty>]
member val Pattern : string = null with get, set
/// Array of filename wildcards.
[<Parameter(Position = 1)>]
[<ValidateNotNull>]
member val Include = [|"*"|] with get,set
/// Whether or not to recurse from the current directory.
[<Parameter>]
member val Recurse : SwitchParameter = SwitchParameter(false) with get, set
/// Encoding to use when reading the files.
[<Parameter>]
member val Encoding = Encoding.ASCII with get, set
/// Toggle for case-sensitive search.
[<Parameter>]
member val CaseSensitive : SwitchParameter = SwitchParameter(false) with get, set
/// Do not use regex, just do a verbatim string search.
[<Parameter>]
member val SimpleMatch : SwitchParameter = SwitchParameter(false) with get, set
... With this kind of TypeProvider anyone who implemented a console CLI parser could get the equivalent posh Cmdlet for free ArgParserProvider<...>To push it even further a TypeProvider could be implemented to generate the Argu DUs that implement ArgParser in the first place Instead of type NewCommand =
| [<First>][<CLIAlt "project">] Project
| [<First>][<CLIAlt("file","-f","fl")>] File
| Nada
interface IArgParserTemplate with
member this.Usage =
match this with
| Project -> "Creates new project"
| File -> "Creates new file"
| Nada -> "doesn’t do much" An // library implementations
type ArgData = {
Case : string
Alts : string
Attrs : string list
Msg : string
Handler : string -> unit
}
let argData case alts attrs msg hdl =
{ Case = case; Alts= alts; Attrs=attrs, Msg = msg; Handler = hdl }
// user code
type NewCommand =
ArgParserProvider<
[ argData "Project" ["project"] ["First"] "Creates new project" projFn
; argData "File" ["project"] ["First"] "Creates new file" fileFn
; argData "Nada" [] [] "doesn’t do much" nadaFn
]
> And then if this generated type is fed into the UnionUtilityProvider<...>A TypeProvider used to enhance DUs that takes a type that satisfies type UnionUtility =
| ToString = 0
| Parse = 1
| ParseCaseInsensitive = 2
| TryParse = 3
| TryParseCaseInsensitive = 4
type MyDU = UnionUtilityProvider< typeof<MyDUBase>, ToString|||Parse|||TryParse > Another variant of this kind of provider could take an enum and transform it into a DU where it has an additional transformation method (e.g. FromInt ) that matches against the backing value type of its cases. StaticMemberProvider<...>A TypeProvider that takes a type and generates corresponding curried BehaviorProvider<...>Working with WPF entails a lot of boilerplate - type BlockShift() as self =
inherit Behavior<ScrollBar>()
let subscriptions = ResizeArray<IDisposable>()
static let depReg<'a> name = DependencyProperty.Register(name,typeof<'a>,typeof<BlockShift>)
[<DefaultValue(false)>]
static val mutable private SizeSmallProperty : DependencyProperty
[<DefaultValue(false)>]
static val mutable private SizeBigProperty : DependencyProperty
[<DefaultValue(false)>]
static val mutable private AttachedToProperty : DependencyProperty
[<DefaultValue(false)>]
static val mutable private BlockZoneProperty : DependencyProperty
static do
BlockShift.SizeSmallProperty <- depReg<float> "SizeSmall"
BlockShift.SizeBigProperty <- depReg<float> "SizeBig"
BlockShift.AttachedToProperty <- depReg<FrameworkElement> "AttachedTo"
BlockShift.BlockZoneProperty <- depReg<float> "BlockZone"
member __.DisposeOnDetach disposable = subscriptions.Add disposable
member __.SizeSmall
with get() = self.GetValue BlockShift.SizeSmallProperty :?> float
and set (v:float) = self.SetValue ( BlockShift.SizeSmallProperty, v )
member __.SizeBig
with get() = self.GetValue BlockShift.SizeBigProperty :?> float
and set (v:float) = self.SetValue ( BlockShift.SizeBigProperty, v )
member __.BlockZone
with get() = self.GetValue BlockShift.BlockZoneProperty :?> float
and set (v:float) = self.SetValue ( BlockShift.BlockZoneProperty, v )
member __.AttachedTo
with get() = self.GetValue BlockShift.AttachedToProperty :?> FrameworkElement
and set (v:FrameworkElement) = self.SetValue ( BlockShift.AttachedToProperty, v ) There's barely any distinct content in this type definition, we could probably cut this down to type BlockShift =
BehaviorProvider<
[ depReg<float> "SizeSmall"
; depReg<float> "SizeBig"
; depReg<FrameworkElement> "AttachedTo
; depReg<float> "BlockZone"
]
> ViewModelProvider<...>Defining a ViewModels type also involves far too much boilerplate type QueryBoxConfig = {
TextColor : Color
BlockColor : Color
ScrollVisibility : ScrollBarVisibility
HighlightColor : SolidColorBrush
ResultWidth : float
ScrollOpacity : float
BlockWidth : float
}
type QueryBoxViewModel<'DataType>(config:QueryBoxConfig, filterfn:string->'DataType->'DataType) =
inherit ViewModelBase()
let ( =+= ) arg1 arg2 = self.Factory.Backing(arg1,arg2)
// I've defined plenty of viewmodel types with 3X this number of properties
let textColor = <@ self.TextColor @> =+= config.TextColor
let blockColor = <@ self.BlockColor @> =+= config.BlockColor
let scrollVisible = <@ self.ScrollVisibility @> =+= config.ScrollVisibility
let highlightColor = <@ self.HighlightColor @> =+= config.HighlightColor
let resultWidth = <@ self.ResultWidth @> =+= config.ResultWidth
let scrollOpacity = <@ self.ScrollOpacity @> =+= config.Opacity
let blockWidth = <@ self.BlockWidth @> =+= config.BlockWidth
member __.TextColor
with get() = textColor.Value and set v = textColor.Value <- v
member __.BlockColor
with get() = blockColor.Value and set v = blockColor.Value <- v
member __.ScrollVisibility
with get() = scrollVisible.Value and set v = scrollVisible.Value <- v
member __.HighlightColor
with get() = highlightColor.Value and set v = highlightColor.Value <- v
member __.ResultWidth
with get() = resultWidth.Value and set v = resultWidth.Value <- v
member __.ScrollOpacity
with get() = scrollOpacity.Value and set v = scrollOpacity.Value <- v
member __.BlockWidth
with get() = blockWidth.Value and set v = blockWidth.Value <- v
// with a concrete form to back a specific GUI control
type CommandPaletteControlVM() =
inherit QueryListBoxViewModel<CommandData>(paletteConfig, filterPalettefn) Unfortunately a Concrete viewmodel type is needed because parametrically polymorphic types This could probably be cut down to type CommandPaletteControlVM =
ViewModelProvider<
{ TextColor = Brushes.LightGray.Color
BlockColor = Brushes.DarkCyan.Color
ScrollVisibility = ScrollBarVisibility.Hidden
HighlightColor = Brushes.Black
ResultWidth = 4.5
ScrollOpacity = 0.40
BlockWidth = 5.0
} : QueryBoxConfig
, typeof<CommandData
, filterPalettefn
> PhantomProvider<...>I'm not sure this can work, or what the proper syntax would be, but the idea type Message<'Tag> = PhantomProvider<string>
type DocumentBlock<'Tag> = PhantomProvider<char []>
type Letter = class end
type Email = class end
type SmokeSignal = class end
type Header = class end
type Footer = class end
let fn1 (block:DocumentBlock<Header>) : DocumentBlock<Header> =
block.GetLowerBound() |> ...
let fn2 (block:DocumentBlock<Footer>) : DocumentBlock<Footer> =
block.[0..5] |> ...
let fn3 (dispatch:Message<SmokeSignal>) : Message<Email> =
dispatch.Split [|';' ; '|' |]... Further Potential (the crazy stuff)Perhaps we could define TypeProviders that require the input type to implement an interface or It might also be possible to implement a TypeProvider similar to Nitra
The |
Beta Was this translation helpful? Give feedback.
-
With the latest version, I’m not sure Toby would be the best person to ask
as he has taken over the implementation as part of his MSc.
That being said the version I had would have exposed the private methods.
However because this was a light weight form of reflection over types, I
wasn’t exposing the method body or invoke. So ultimately I’m not sure how
useful it would be to you.
I cant imagine the new version is that different it just utilises the new
TPSDK and the .net core support that comes with that.
…On Sun, 29 Apr 2018 at 13:20, John Moreno ***@***.***> wrote:
@colinbull <https://github.com/colinbull> : does it support exposing
private methods/properties of a class?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<https://github.com/fsharp/fslang-design/issues/125#issuecomment-385247461>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAjvSoDfa0uuIJoL9RCt0XT8t_gF77Wpks5ttbAPgaJpZM4KEaVu>
.
|
Beta Was this translation helpful? Give feedback.
-
@colinbull : does it support exposing private methods/properties of a class? |
Beta Was this translation helpful? Give feedback.
-
So, with the option to pass in types, there comes a few questions about how exactly they get passed in.
I think both have their usecases, and I think we should be able to do both, depending on whether the type variable exists in the scope of a type-level function or a value-level function. For 1. it would look like: For 2. it would look like: What do you think of this idea? |
Beta Was this translation helpful? Give feedback.
-
Thought I'd leave a comment here on a few things I've done on this. I spent a small amount of time refactoring the test suite for this change, it should be easier to extend with more tests now: here In terms of notable changes to the actual code: I updated it to use the latest TPSDK, (types passed in basically ignore the target/design-time conversion process, as they always live in the target). To get FSharp reflection working, I added the CompilationMappingAttribute to the reflected types: here I'd hope that this would allow the use of TypeShape in type providers, but this is not the case. TypeShape is incompatible with these reflected types, due to its design. To get around this, one could use a type provider purely as a validation step before passing to a runtime-typeshape library. This would give the benefits of type-checking the type parameter, but without the code generation aspect. I also fixed a lot of small things, like Namespace was incorrect, and the fully qualified names were also subtly wrong. The attributeflags will need a bit of work to get exactly right. Currently I've got most flags set to sensible defaults, but eventually they will need to be set properly: here I know that in order to get the IsNested function working, I need to synthesize an enclosing type corresponding to the Module, and report this as a top-level type's DeclaringType (IsNested is implemented in terms of DeclaringType). On another thread, I also had a go at implementing passing of type variables. Currently it only works with bound type variables passed into provided methods, but there's a few things you could still theoretically do with it so far: here It's not that much, and I'd hoped to have more done at this point. On the flipside, exams are finally over, and I spent a lot of time understanding various aspects of the compiler and authoring providers. I hope to make lots more progress in the next month. |
Beta Was this translation helpful? Give feedback.
-
@colinbull added this: As stands currently stands the following things have a implementation (some with far rougher edges than others) • Support for custom attributes. Off the top of my head the following things are missing or I have no idea about the current behavior • Support of events? |
Beta Was this translation helpful? Give feedback.
-
I wrote up a sent of notes for Toby Shaw on @colinbull's prototype before Christmas, I'm pasting it here for safe keeping:
In the long term it’s possible that the TastReflect implementation could replace both TypeSymbol and TargetTypeDefinition. However it’s best to keep them separate for now In the short term, the implementations in the TPSDK are more complete and “better” than the ones – Colin copied/adjusted the early TPSDK implementations but I found a whole host of problems with them after that, and largely rewrote them. I’d recommend merging the “best bits” of TypeSymbol/TargetTypeDefinition with the “best bits” of TastReflect.fs.
|
Beta Was this translation helpful? Give feedback.
-
Latest is here, https://github.com/colinbull/visualfsharp/tree/rfc/fs-1023-type-providers not worked on it in about 2 months, since I have been on a large time consuming project at real work :(. But hoping to get back to it soon. |
Beta Was this translation helpful? Give feedback.
-
I hope this doesn't die out. It's by far the most exciting thing happening in F# land these days. |
Beta Was this translation helpful? Give feedback.
-
@Thorium the problem is 'MyRecord' in this case would have to be provided in an external assembly which is loaded via reflection, or Compiled with the TP. |
Beta Was this translation helpful? Give feedback.
-
No workarounds even? There is a baseType in ProvidedTypes.fs: type ProvidedTypeDefinition(container:TypeContainer, className : string, baseType : Type option, convToTgt) = ...and method can return a type, so I name my method as "GetBaseItem": ProvidedMethod("GetBaseItem",[],typeof<MyRecord>,...) or dynamically let returnType =
let rt = ProvidedTypeDefinition("MyGeneratedType",Some typeof<MyRecord>, HideObjectMethods = true)
//...
rt :> Type
ProvidedMethod("GetBaseItem",[],returnType,...) |
Beta Was this translation helpful? Give feedback.
-
I wonder if intrinsic type extensions along with this allows even more options? |
Beta Was this translation helpful? Give feedback.
-
Intrinsic type extensions + this RFC is what I was imagining for GUI designer codebehind (where type MyViewController = designable<UIViewController> with
override this.ViewDidLoad() = this.MyLabel.Text <- "Hello World!" This would yield a generative type equivalent to: [<Register("MyViewController")>]
type MyViewController(handle : IntPtr) =
inherit UIViewController(handle)
// Some outlet declared in the designer
[<Outlet("MyLabel")>]
member val MyLabel = Unchecked.defaultof<UILabel> with get, set
override this.ViewDidLoad() = this.MyLabel.Text <- "Hello World!" In this case, the type argument specifies the base class to use for the generated type. |
Beta Was this translation helpful? Give feedback.
-
My absolute favorite use case would be creating a Lens TP. type Foo = { a: int; b: string option }
type FooLenses = LensProvider<typeof<Foo>> so that let fl = new FooLenses()
let foo = { a = 1; b = Some "foo" }
let aval = fl.a.get foo
// aval = 1
let foo' = fl.b.set "bar" foo
// foo' = { a = 1; b = Some "bar" } Also this should be possible for generic types. so that type Bar<'a, 'b> = { a: 'a; b: 'b }
type BarLenses = LensProvider<typeof<Bar<_,_>>> |
Beta Was this translation helpful? Give feedback.
-
Another scenario I have in mind is some emulation of Higher Kinded Types type ChangedItem<'a> = { value: 'a; changedAt: DateTime }
type WorkItem<'a> = {actual: ChangedItem<'a>; prevValues: ChangedItem<'a> list option}
type ClosedItem<'a> = {value: 'a; prevValues: ChangedItem<'a> list}
type WorkingFoo = {
a: WorkItem<String>;
b: WorkItem<int>
}
type ClosedFoo = ClosingTypeProvider<typeof<WorkingFoo>> Where type ClosedFoo = private {
a: ClosedItem<String>;
b: ClosedItem<int>
}
with
member a = this.a
member b = this.b |
Beta Was this translation helpful? Give feedback.
-
The thing to remember is this allows types as input, the scope does not
include what exactly can be outputted. I think tightly scoping the output
terms would be required.
For example outputting records from a type provider
is an entirely different RFC.
|
Beta Was this translation helpful? Give feedback.
-
Well, as long as one knows how a record is implemented with c#/.net there is probably nothing one couldnt do ;-) |
Beta Was this translation helpful? Give feedback.
-
@kurtschelfthout @7sharp9 @cloudRoutine @ReedCopsey @robkuz Based on 30minutes on the sofa with @colinbull plus considerable follow-up hacking we have a spectacularly early POC of this feature https://github.com/colinbull/visualfsharp/tree/rfc/fs-1023-type-providers |
Beta Was this translation helpful? Give feedback.
-
We should combine this with intrinsic type extensions for maximum wonga. |
Beta Was this translation helpful? Give feedback.
-
@dsyme Is the current cut erasing only? |
Beta Was this translation helpful? Give feedback.
-
I don't know of any specific reason why generative wouldn't work |
Beta Was this translation helpful? Give feedback.
-
I would try but vfsharp is currently broken on osx due to the resources
issue.
…On 12 Apr 2017 6:22 pm, "Don Syme" ***@***.***> wrote:
@dsyme <https://github.com/dsyme> Is the current cut erasing only?
I don't know of any specific reason why generative wouldn't work
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<https://github.com/fsharp/fslang-design/issues/125#issuecomment-293648979>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAj7ytwo3YiS2cbquqh2JWOamTWnvqNeks5rvQhPgaJpZM4KEaVu>
.
|
Beta Was this translation helpful? Give feedback.
-
Let's get that fixed. Could you try shifting VFT to the latest FSharp.Compiler.Tools? |
Beta Was this translation helpful? Give feedback.
-
@dsyme Could we get the osx fix cherry picked so this is easily testable? The main pain points are I also have to port this to FCS to get tooling support. |
Beta Was this translation helpful? Give feedback.
-
It would be greatly useful to have a compilation flag in visualfsharp repository which compiles "a source compatible with FCS" assembly roughly compatible with FCS, it means workflow in visualfsharp would allow to test/integrate in tooling outside of Visual Studio. |
Beta Was this translation helpful? Give feedback.
-
@smoothdeveloper afaik the only thing that's needed is conditionally make the symbols api public. That's it. |
Beta Was this translation helpful? Give feedback.
-
This is the discussion thread for RFC FS-1023.
Beta Was this translation helpful? Give feedback.
All reactions