3

Github Support attributes on lambda expressions · Issue #984 · fsharp/fslang-sug...

 3 years ago
source link: https://github.com/fsharp/fslang-suggestions/issues/984
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

New issue

Support attributes on lambda expressions #984

cartermp opened this issue on Feb 24 · 33 comments

Comments

Copy link

Member

cartermp commented on Feb 24

edited

I propose we support attributes on lambda expressions. A way this could get used is in the experimental aspnet Houdini project, which is experimenting ways to cut the cruft in aspnet core service development, including an alternate routing system than MVC. One of their approaches would be mapping endpoints with lambdas:

record Todo(int Id, string Name, bool IsComplete);

app.MapAction([HttpPost("/")] ([FromBody] Todo todo) : Todo => todo);
app.MapAction([HttpGet("/")] () : Todo => new(Id: 0, Name: "Play more!", IsComplete: false));

Note that the lambda itself has an atttribute. In F#, the above snippet would probably look something like this:

type Todo = { Id: int; Name: string; IsComplete: bool }

app.MapAction([<HttpPost("/")>] fun ([<FromBody>] todo) -> todo) |> ignore
app.MapAction([<HttpGet("/")>] fun () -> { Id = 0; Name = "Play more!"; IsComplete = false }) |> ignore

The existing way of approaching this problem in F#, using the above example, would be to pull out the lambda bodies into separate functions and pass them into a constructed Func:

type Todo = { Id: int; Name: string; IsComplete: bool }

[<HttpPost("/")>]
let echoTodo ([<FromBody>] todo) = todo

[<HttpGet("/")>]
let getTodo () = { Id = 0; Name = "Play more!"; IsComplete = false }

app.MapAction(Func<Todo,Todo>(echoTodo)) |> ignore
app.MapAction(Func<Todo>(getTodo)) |> ignore

This isn't really a bad alternative though, the main thing is that it's annoying to have to construct a func. Instead, lobbying to get MapAction to support equivalent FSharpFunc types as overloads and simply have those overloads construct the appropriate Func type could make this approach nice from an F# perspective.

However, I still think it's worth considering (even if people decide it's not really useful for F#) since this is an approach to programming against an API that more library authors may take in the future.

Pros and Cons

The advantages of making this adjustment to F# are:

  • Orthogonality I suppose, since you can decorate an F# function with an attribute. If you do this with a function and then want it to also work with a lambda, you're fine. Although lambdas aren't 100% in correspondence with a function - see use of byrefs for more information.
  • Works nicely with APIs that expect people to use this sort of pattern

The disadvantages of making this adjustment to F# are:

  • Allows for "call site complication" with lots of information stuffed into a lambda, making seemingly succinct code somewhat complex / difficult to understand
  • Encourages stuffing lots of functionality into a lambda expression. Although this is somewhat common, especially in longer pipelines where you don't have a need to pull out an expression into its own function since it's only used once, it's still not "typical" F# code.
  • Encourages a way to interact with APIs that we may not want or need
  • Not terribly discoverable

Extra information

Estimated cost (XS, S, M, L, XL, XXL): M

Related suggestions: (put links to related suggestions here)

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this

For Readers

If you would like to see this issue implemented, please click the +1 emoji on this issue. These counts are used to generally order the suggestions by engagement.

Copy link

charlesroddie commented on Feb 24

edited

ways to cut the cruft in aspnet core service development

So near and yet so far.

A lot of object-oriented programming styles are so averse to using objects.
I wonder why they do not go all the way and replace all type information with annotation?

[<HttpGet("/")>]
[<Id(0)>]
[<Name<"Play more!">>]
[<IsComplete(false)>]
let todo = object()

Then everything is object()!

Overall MapAction seems uninteresting to F# because it's intrinsically annotation-based and has weird JSON dependencies which imply that everything is implicit not explicit. F# should not support this sort of thing. Intstead there should be an action creator with a genuine type signature, such that when given objects of the right types the code will work.

Copy link

Contributor

Happypig375 commented on Feb 25

We can use this to support static lambdas, if we decide to do it.

Copy link

halter73 commented on Feb 25

edited

We're planning to make the route pattern an explicit parameter again rather than take it from an attribute which should hopefully reduce "call site complication". dotnet/aspnetcore#30448

With this change, the F# example would look more like the following:

type Todo = { Id: int; Name: string; IsComplete: bool }

app.MapPost("/", fun ([<FromBody>] todo) -> todo) |> ignore
app.MapGet("/", fun () -> { Id = 0; Name = "Play more!"; IsComplete = false }) |> ignore

Copy link

charlesroddie commented on Feb 25

edited

@halter73 An API that respects the .Net type system would avoid nongeneric classes like Delegate, annotations like [<FromBody>]. It would be something like:

MapPost(string pattern, System.Func<System.Web.HttpRequest, System.Web.Mvc.ActionResult> poster)
MapGet(string pattern, System.Func<System.Web.Mvc.ActionResult> getter)

Copy link

davidfowl commented on Mar 5

Then we'd need infinity (hyberbole) overloads. Which makes things even more complex (any number of arguments, sync vs async, ValueTask vs Task)

Then we'd need infinity (hyberbole) overloads. Which makes things even more complex (any number of arguments, sync vs async, ValueTask vs Task)

Sync/ValueTask/Task is 3. Why would you need "any number of arguments"?

Copy link

halter73 commented on Mar 5

Why would you need "any number of arguments"?

For convenient parameter binding. So instead of having let! todo = httpRequest.ReadFromJsonAsync<Todo>() |> Async.AwaitTask, the framework can do this for you before calling a user-defined Action<Todo> or Func<Todo, Task<MyResponse>> or whatever.

If a developer also wants to get an id from the route (ex: "/api/todos/{id}"), they can add an int parameter instead of calling let id = int httpRequest.RouteValues.["id"] and having to somehow deal with invalid ints. By adding an int parameter named id, you declaratively tell the framework that you expect the {id} part of the route to be an int and to not even call into user code if it's not.

Copy link

davidfowl commented on Mar 5

Sync/ValueTask/Task is 3. Why would you need "any number of arguments"?

We can even natively support F# async without defining that overload.

Because we want to support binding arguments from various sources:

  • Route
  • QueryString
  • Header
  • Cookie
  • Request Body
  • Multipart Form
  • Services

The beauty of supporting any function type is that we can add support for new things in the future, without adding more overloads.

@cartermp It occurs to me that this is just one of the many features we need to make this work smoothly on F#. The other is around supporting conversion from F# lambdas to Delegate without an explicit cast.

Copy link

dustinmoris commented 12 days ago

edited

So thanks to @davidfowl I'm going to leave some feedback here, but unfortunately I have many random thoughts which are not strictly limited to this specific issue. I appreciate that this is not the right place to log them all, but if it's ok with you I'll dumb my thoughts here for now and then move any actionable issue elsewhere with your guidance.

Ok, so here we go...

First I welcome the changes in ASP.NET Core to introduce a more flexible API which feels closer to the metal. At least this is what my impression is by looking at what you guys are working on and I think it's a great idea!

Personally I think the current approach is not bold enough. It's just another "big" framework with a slightly different syntax. IMHO the approach taken tries to solve too many responsibilities at once. The new Houdini style framework tries to appease to too many developers, people like myself who want to have simple to use and flexible lower level API as well as to the "spoilt" developer who just wants a "plug & play" MVC-style API. I think trying to achieve both with a single framework will ultimately fail to deliver the right abstraction which can appeal to both groups and will only change the perception temporarily at best.

If it was my decision then I'd suggest to create a very low level (almost Go like) HTTP API which is super flexible and truly bare bones which gives developers maximum control and yet a good enough convenience API which can empower the minimalist developer to feel extremely productive and not having to fight the tools. Using those low level APIs Microsoft could create a second higher level "plug & play" framework for the easy beginner friendly path. Currently this was meant to be ASP.NET Core and MVC, but I think they failed because the lines were not clearly drawn and many ASP.NET Core APIs were specifically built with MVC in mind and not thought of as a standalone library. I almost think that both these things should get built by different teams to avoid the same mistakes and not mud the waters.

Now taking a bit more specifically about the low level API which I'd like to see. I don't think it needs (or should) have attributes at all. Declarative APIs are very high level. It means there is something else sitting below which expects certain idioms to be followed in order to carry out useful tasks. If something doesn't follow these idioms then things stop working or fall apart. At least things will not work entirely as advertised which is what will users give the feeling of fighting the tools. Regardless how well one thinks they have designed those idioms, you can only design what you anticipate for. Developers will always want to do something new, something unconventional, something which seemed silly one day but makes sense today and then a declarative API will quickly show its cracks.

Therefore I suggest to keep it truly low. No attributes, no declarative model binding. Just have low level functions such as

  • ctx.Request.TryBindModel<T>
  • ctx.Response.WriteAsync(bytes, ct)
  • ctx.Response.SetHeader("Content-Type", "application/json")

Does it mean that things like authentication through a single middleware might not work or that another middleware won't be able to produce comprehensive Swagger docs? Yes, but that is ok. I would even suggest that concerns such as authentication and CORS should be moved closer to the endpoints where they are actually being required and not sit in a global middleware which therefore relies on a heavy declarative API in order to centrally control every single endpoint.

Again, this only has to be true for the low level API. Move things closer to where it is required. Having something like app.MapAction("/foo", requiresBasicAuth(cors((someHandler))) would be really nice.

Yes it's ugly, but it's low level beauty. People who will use that will build their own meaningful wrappers and APIs to build the app which they want. It empowers them and gives them 100% control and lets them create the beautiful APIs which they see fit.

On top of these simple idioms you can have a separate attribute-driven API which gives you a more out of the box experience. Of course, in return this will force a user down a certain path but it's a trade-off that people will accept when they know that they can break out into a lower level world if they know that exists.

The other thing which I would try to keep in mind is really the naming of things and how it affects the beauty of APIs. @davidfowl mentioned it on Twitter and I couldn't agree more. Beauty is important and in my opinion a big principle of beauty is to ensure that the name of a method or function must match the complexity underneath, otherwise people will think it's magic or too verbose.

For example, mapping a single string to a route feels like a very trivial task. A method name like MapAction doesn't strike me as a great name as it's unnecessarily long and verbose:

app.MapAction([HttpPost("/")] ([FromBody] Todo todo) : Todo => todo);

Better:

app.GET("/", ctx => someFunctionToHandle)

However, if something is more complex then it is ok to have a slightly longer name.

var model = ctx.Bind<T>()

Good:

var model = ctx.Request.Body.Bind<T>()

Now coming back to F#....

Overall I think it would be a mistake to introduce Attributes as a core concept into F#. We already have them, but their use is very limited and often only in relation to C# interop, but such a change would make attributes a first class citizen which feels wrong if it's only needed in order to mould F# around a new .NET web framework which couldn't let go of a declarative API.

EDIT:

Until ASP.NET Core offers true low level APIs which don't need attributes or where endpoints must rely on centrally controlled authentication/CORS/etc middlewares then developers will always come and label the current framework as too heavy and too bloated because if they reject the existing API they are left with very little to no other options. Please believe me, I can guarantee you 100% that this will be the case because it's always been like that.

That's really good feedback, feel free to close this issue. I see now that this isn't idiomatic at all in F# abs users are better served by something like giraffe. We'll definitely consider adding something like bind (though we do that already have some bring for JSON, and it's not clear that we need any more than that).

As for the requireAuth(cors(...)) I'm not sure we'd add something like that since we already have an imperative way of adding metadata to endpoints.

Again, this is good feedback, muchly appreciated

Copy link

Member

Author

cartermp commented 12 days ago

@davidfowl

The other is around supporting conversion from F# lambdas to Delegate without an explicit cast.

F# supports direct conversions at the call site like so:

open System
type C() =
    member _.M(f: Func<int, int>) = ()
    member _.M(f: Action<int>) = ()
    
let c = C()

c.M(fun x -> x + 1)  // direct func interop
c.M(fun x -> printfn $"{x}") // action interop

let funcThing = fun x -> x + 1
let actionThing = fun x -> printfn $"{x}"

c.M(funcThing)  // direct func interop
c.M(actionThing) // action interop

But there can be some gaps, and it'd probably ease current interop patterns with ASP.NET Core. In the preview bits I played with for some reason I couldn't just interop directly like I can above, so that could be addressed separately.

As for the general point @dustinmoris and @davidfowl I don't think there's much harm in F# adopting smoother .NET interop support for existing constructs like this. It is also a matter of orthogonality that is worth discussing - if you can put an attribute on an F# function, why not a lambda when they have near-identical correspondence? I think there are certainly some drawbacks to using it, and I would personally prefer not to use an API that requires lots of attributes. But that's also why we have wonderful choices like Giraffe, Saturn, Falco, Suave, etc.

@davidfowl perhaps a better way to ease F# use of aspnetcore is to work out a few of the pain points with @dustinmoris and identify things where:

  • aspnetcore could make a small addition for F# libraries that sit atop the primitives to work smoother (e.g., ease configuration or use of application settings)
  • F# language could improve interop (e.g., what it takes to ensure no explicit Func or Action construction is done at a call site for MapAction)

I don't have context on the former piece, but I'd see it as akin to MVC supporting routes that are written directly as F# async. When that was done, MVC apps didn't need to call Async.StartAsTask at the end of each route if they wanted to keep using F# async.

Copy link

Collaborator

dsyme commented 12 days ago

But there can be some gaps, and it'd probably ease current interop patterns with ASP.NET Core. In the preview bits I played with for some reason I couldn't just interop directly like I can above, so that could be addressed separately.

FWIW these issues are, I believe, ironed out by https://github.com/fsharp/fslang-design/blob/master/RFCs/FS-1093-additional-conversions.md. However we should trial and check (it may not apply to overloaded cases for example).

I don't think there's much harm in F# adopting smoother .NET interop support for existing constructs like this. It is also a matter of orthogonality that is worth discussing - if you can put an attribute on an F# function, why not a lambda when they have near-identical correspondence?

I agree we should address this as it's a basic matter of interop-capability for F# and we'll inevitably encounter attribute-aware .NET frameworks

Copy link

halter73 commented 11 days ago

edited

The other is around supporting conversion from F# lambdas to Delegate without an explicit cast.

I guess this got lost when this got turned into an issue. Here's the original email for context.

Hi Phillip,

I'm reaching out because we're planning to add support for lambda attributes in C# 10 and I'm wondering if this also makes sense for F#. It's going to be important for a MapAction() API we're planning to add to ASP.NET.

Here's a small example of using MapAction with and without the new lambda features in C#:

Comparing main..lambda-attributes-return · halter73/HoudiniPlayground (github.com)

And the comparison in F#:

Comparing f1b94d2...b70aa73 · halter73/HoudiniPlaygroundFSharp (github.com)

Aside from supporting attributes on lambdas and lambda arguments, this also relies on lambdas having a "natural type" when assigned to a "Delegate" (the type expected by MapAction). We want to avoid an error like the following:

C:\dev\halter73\HoudiniPlaygroundFSharp\Startup.fs(24,36): error FS0193: Type constraint mismatch. The type ↔ 'unit -> int' ↔is not compatible with type↔ 'Delegate'

Even trying to downcast with :?> Func<Todo> fails with the following, so the "before" in my F# before and after sample doesn't even compile today.

C:\dev\halter73\HoudiniPlaygroundFSharp\Startup.fs(31,37): error FS0016: The type 'unit -> Todo' does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion.

Do you think this is something F# could support? I think an API like MapAction lends itself to interactive programming of the type F# seeks to enable. In fact, I've been using .NET Interactive for a lot of testing. It's been with C# so far, but I'd like to be able to use F#!

@cartermp Then contributed some PRs to the HoudiniPlaygroundFSharp repo that's gotten it closer to something that could theoretically work: halter73/HoudiniPlaygroundFSharp#1 and halter73/HoudiniPlaygroundFSharp#2, but neither version works yet the MapPost doesn't work yet because ASP.NET cannot see the [<FromBody>] attribute on todo.

Edit: The MapGet does work because there aren't any attributes required.

Right I'd like the examples that @halter73 posted to work with F# as well, attributes aside

Copy link

halter73 commented 11 days ago

edited

We have a new design at dotnet/aspnetcore#30248 that will infer the sources for some parameters without attributes that follow conventions common to ASP.NET Core MVC.

Because of these conventions, even the [<FromBody>] attribute on the POST can now be removed leaving no attribute left in the original sample!! Attributes will still be important to get the full flexibility of the API.

After the changes, the following will work. The MapGet already works today if you use the nightlies.

type Todo = { Id: int; Name: string; IsComplete: bool }

app.MapPost("/", Func<Todo,Todo>(fun todo -> todo)) |> ignore
app.MapGet("/", Func<Todo>(fun () -> { Id = 0; Name = "Play more!"; IsComplete = false })) |> ignore

Having to write out Func<Todo,Todo> feels a lot like the unnecessary casting we currently have to do in C#, but since that allows for type inference in the lambda definition maybe it's not so bad in F#.

Copy link

charlesroddie commented 11 days ago

edited

@dustinmoris If it was my decision then I'd suggest to create a very low level (almost Go like) HTTP API which is super flexible and truly bare bones with maximum control but good enough in terms of convenience so that power users can be productive without fighting the tools and then a second plug&play framework on top of that low level API for the easy beginner friendly path.

YES. And the low level API should be a type-safe one. The convenience API can be completely designed with typical C# devs in mind.

@davidfowl and @halter73 were suggesting that for convenience we should lose static typing and use a dynamic convention-based system.

  • @davidfowl mentioned "binding arguments from various sources", but F# does not have a problem with expressing logic and works well with optional types so extracting data from sources isn't a problem. TBH it's pretty straightforward in C# too.

  • @halter73 gave a more difficult example "/api/todos/{id}" where something extra tells the system that id is an int. Let's assume this is "/api/todos/{INT id}". Here there is the fundamental type and then convenience. The fundamental type takes the path-like string and returns a Func<HttpRequest, ActionResult>?, which if null means that the route is not followed at all. It's straightforward to write this directly if you don't mind being explicit about parsing. For a convenience layer, that will be language specific, because different languages want different things and because the current code-generation strategy is different across languages. C# can define source generators or use your dynamic idea. F# could define a type provider: Get<"/api/todos/{INT id}">.(fun (httpReq, pathArgs) -> pathArgs.Id ... ) // pathArgs has an int Id property.

The ASP.Net team shouldn't need to bother about a convenience layer for F# since we should be able to use a type-safe layer directly or write our own convenience layer. The important thing is that all of this can be built on top of a type-safe core API. What is not acceptable is forcing people to use a dynamic API in .Net! Any dynamic API should be optional (nuget package or linker-compatible so it can get linked out if not used).

Thanks @charlesroddie. We don't want to force anyone to use anything and want to expose the right building blocks so other libraries can build idiomatic layers. We already have a strongly typed core built around a strongly typed RequestDelegate (giraffe is built on top of it). We've also added APIs to read and write JSON from and to the request and response and will continue to do so as we push more features into the core as building blocks.

That doesn't change what we're asking for here, but I think it's partially up to the F# community to decide if this is anti F# or not. The building blocks already exist and we will continue to build more of them but we also like the declarative model for C# as it can remove lots of boilerplate in lots of cases. This isn't zero sum.

Copy link

toburger commented 11 days ago

edited

That doesn't change what we're asking for here, but I think it's partially up to the F# community to decide if this is anti F# or not. The building blocks already exist and we will continue to build more of them but we also like the declarative model for C# as it can remove lots of boilerplate in lots of cases. This isn't zero sum.

I think that's the crucial point here:
When speaking of declarative programming in C# often an AOP approach is meant (applying attributes to parameters to control application specific semantics) that weakens the type checking (everything comes together at runtime).
In F# declarative programming is achieved by using F#'s idioms such as expressions (instead of statements), EDSLs and type safe programming which composes at the type checker level.

Copy link

dustinmoris commented 11 days ago

edited
app.MapPost("/", Func<Todo,Todo>(fun todo -> todo)) |> ignore

I don't know what type app is here, but if there was a Router object I'd love to use an API like this:

let router = new Router()
router.GET("/", indexHandler)

...
// During web host creation:
app.Endpoints.MapRouter(router)

app.Run()

Also the |> ignore is extremely annoying in F#. That means that your current MapGet methods return an object to do chained mapping probably like MapGet(....).MapPost(...). I wonder if that is really required? Is this how YOU see the API should be used? If not and if all examples map one route per statement then why not just make it a void method and then F# developers don't have to ignore everything? I'm just asking because it seems that in C# you actually don't really envision it to be chained so if you change the return type to match your own expectations then you'd make a huge difference to F# developers as well! The other alternative would be to have an overload or another set of the same methods just voided so that it's more natural from F#.

EDIT:

That's really good feedback, feel free to close this issue.

@davidfowl I didn't mean to put a downer on the Houdini API or suggest that this issue should get closed. Clearly the ASP.NET team sees a desire for a web framework which appeals more to the minimalist developer and I only wanted to share some thoughts of someone who considers themselves a minimalist developer with what I thought could be a problem. I think the developers who want to be productive with a higher level declarative API are already very well served with MVC and I think the new Houdini API doesn't necessarily have to compete with MVC but maybe could focus more on what MVC doesn't do well today.

I don't know how much is already set in stone, but I'm happy to provide as much constructive feedback as you think is still helpful at this point :)

Copy link

halter73 commented 11 days ago

edited

The app is an IEndpointRouteBuilder. The returned object isn't for chained mapping like MapGet(....).MapPost(...). It's for adding metadata to the route for things like auth.

app.MapPost("/", Func<Todo,Todo>(fun todo -> todo)).RequireAuthorization() |> ignore

I agree the |> ignore is extremely annoying though.

If you're interested the docs are at https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-5.0#routing-concepts

Copy link

dustinmoris commented 11 days ago

edited

Right, I see....

Another option which I think could work nicely for C# and F# could be by making the handler method the final void part of the chain, forcing metadata to be set beforehand:

 app.POST("/api/foo").RequiresAuth().EnableCors().Handle(fun todo -> todo)

However, the problem only exists because your current framework requires those extra metadata functions in order to feed the information into a central auth middleware and other middlewares. If The API was "lighter" then none of that would even be an issue to begin with :)

Copy link

dustinmoris commented 11 days ago

edited

Also, re naming, see how my example reads so much simpler and quicker and allows a developer to comprehend what is happening than the version where everything is stretched much longer with MapAction/MapPost, RequireAuthorization, ...

app.MapPost("/", Func<Todo,Todo>(fun todo -> todo)).RequireAuthorization() |> ignore

vs

app.POST("/").RequiresAuth().Handle<Func<Todo,Todo>>(fun todo -> todo)

The benefit of a final Handle chain function could also solve the type casting like so:

type TodoHandler = Func<Todo,Todo>
app.POST("/").RequiresAuth().Handle<TodoHandler>(fun todo -> todo)

Copy link

halter73 commented 11 days ago

edited

Personally, I think void Handle method at the end of the chain reads well and like that it gets rid of the |> ignore. We already ship MapGet/MapPost/... RequestDelegate overloads with the metadata chained at the end though, so it doesn't really make sense to switch conventions at this point. I do think libraries like giraffe can help here by building more idiomatic F# APIs on top of it and similar APIs like RequestDelegateFactory (dotnet/aspnetcore#31181).

Copy link

dustinmoris commented 11 days ago

edited

Also one more thing, I'm not even sure if RequiresAuth() is a good abstraction, because that means it will lead to something like this:

type TodoHandler = Func<Todo,Todo>
app.POST("/").RequiresAuth(fun options ->
    // configure auth options
).Handle<TodoHandler>(fun todo -> todo)

Which leads to chains of builders within builders which are really not nice. They are not nice in production code and they are not nice in demos. Many layers of builders make C#/F# code look like a snake with a lot of indentation going in and out multiple times, a bit like a huge HTML file with tons of nested divs. Just the raw look of it makes things look "huge" and "bloated" and "complicated". This kinds of feeds back into the beauty of APIs again. The more the final code is just a set of simple top level statements the nicer it feels and reads. Just my own opinion.

I'd suggest to stick with the first level of abstraction and just make better use of that like so:

type TodoHandler = Func<Todo,Todo>
app.POST("/").BasicAuth().JWTAuth().SessionCookies().Handle<TodoHandler>(fun todo -> todo)

@halter73

Sure I understand that certain APIs have already been shipped and are set in stone, but if there is still an opportunity to make things nice across all of .NET then it's worth considering.

After all Giraffe is not a different web framework, I never saw it that anyway. It's just a wrapper library to make ASP.NET Core work for F#. Honestly, I wish this wasn't required in the first place if you see what I mean.

Copy link

halter73 commented 11 days ago

edited

I completely agree we should make ASP.NET Core as usable out of the box as possible for F#. I appreciate the input. There's still a lot of open design space for the API, but the other existing overloads for MapGet/MapPost/... do create expectations.

Another API you might be interested in is our new "direct hosting" API dotnet/aspnetcore#30354. It's heavily inspired by Feather HTTP.

Cool will take a look. Thanks for linking!

Copy link

davidfowl commented 11 days ago

edited

That's good feedback about some of the fluent APIs and how it has the potential to complicate the code and make it look like a snake. In practice that doesn't happen because options aren't specified the way you describe.

Also one big design goal is NOT to change the existing idioms we're already created. We're not building a new framework, we're refining what we have carefully so we don't break the existing patterns and can take advantage of all the code that is and the ecosystem has written over the last 5 years. That is an explicit design goal.

PS: I also like the handle method at the end of the fluent chain of calls. We can propose an alternative API for building up a route, that'd be interesting.

Copy link

lambdakris commented 9 days ago

edited

I still program mostly in C# for work and I gotta admit that I really like the look of something like what @dustinmoris suggested, for both F# and C#!

app.Post("/").RequiresAuth().Handle((Todo todo) => 
{ 
  return todo;
});

So I like the idea of looking into an alternative API for building up a route, for C# as much as for F# :)!

P.S. I also support the idea of adding support for Attributes on lambda expressions in F#, if for nothing else than completeness/symmetry

Copy link

Contributor

Happypig375 commented 9 days ago

@lambdakris That's a weird hybrid of C# and F# there

Copy link

Contributor

smoothdeveloper commented 9 days ago

@Happypig375 the kind of weird hybrid that @cartermp code fixers will fix in F# editor smile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Assignees

No one assigned

Projects

None yet

Milestone

No milestone

Linked pull requests

Successfully merging a pull request may close this issue.

None yet

10 participants

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK