Improve computation expression style DSL for Fun.Blazor
source link: https://www.slaveoftime.fun/blog/38
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.
Before in the last post I said there are three limitations when use computation expression for UI DSL:
- The intelliense is not very good
- Fsharp can not always infer the types, sometimes you need to call CAST manually
- For CE (computation expression) style, F# do not support override CustomOperation
Now the 2 and 3 are solved.
For "2. Fsharp can not always infer the types, sometimes you need to call CAST manually"
I solved by add below code to the base class:
type FunBlazorContext<'Component when 'Component :> Microsoft.AspNetCore.Components.IComponent> () =
...
member this.Run _ = this :> IFunBlazorNode
...
So every CE builder inherit from FunBlazorContext can be automatically cast to IFunBlazorNode. In this way we do not need compiler to infer and auto cast its type. So no matter where you compose your UI controls, you do not need below CAST tail call:
MudText'() {
Color Color.Primary
Typo Typo.h5
childContent "Have fun ✌"
CAST
}
For "3. For CE (computation expression) style, F# do not support override CustomOperation"
In fsharp 5 preview I noticed that we have enabled CustomOperation override.
Now the duplicate of generated DslCE is reduced a lot. Take MudBlazor as example, before it will generate 3934 lines of code and now it reduced to 2332 (Some refactor also helps this reducing). Removed the redundant code makes me feel much better now.
Last but not least
I changed some conventions.
Before I use lowercase for the DslCE, for example mudText will be the CE builder for the MudText. All the properties are transformed by lowerFirstCase too. There are two problems with this:
- When you copy their demo code (for example from the MubBlazor docs website), you need to type more code to transform
- Some controls can inherit from base dom, because it can accept all the base dom properties like onclick, value, href etc. Then the property override may not behave as you may expected. For example:
In the base class we have a CustomOperation "value", it accept obj as argument
[<CustomOperation("value")>] member this.value (_: FunBlazorContext<'Component>, v: obj) =
"value" => v |> BoleroAttr |> this.AddProp
In MudSelectItemBuilder, we also have a CustomOperation "value" (if we lowered the first case), but it accept a generic argument then the compiler may not select this property instead it will select the property from the base class. In base class the "value" name is lowercase because blazor`s basic dom define it in that way, but in MudBlazor it accept uppercase as the parameter. And they are totally two different things. A little confusing right? Me too, because when I write the cli, I made that mistake.
[<CustomOperation("value")>] member this.value (_: FunBlazorContext<'FunBlazorGeneric>, x: 'T) =
"Value" => x |> BoleroAttr |> this.AddProp
Anyway, in the last I thought there is no need to change the case of perperty or the class name for CE style DSL. Only transform one property "ChildContent", because sometimes a control inherit from the base class which has "childContent" but sometimes it does not need to inheirt it and its default name will be "ChildContent" if I do not transform it but they are the same thing. So I think change them all to "childContent" is more consistency.
So now the generated CE style code can be consumed like this:
MudDrawer'() {
Open isOpen
Elevation 25
Variant DrawerVariant.Persistent
childContent [
MudDrawerHeader'() {
LinkToIndex true
childContent [
MudText'() {
Color Color.Primary
Typo Typo.h5
childContent "Have fun ✌"
}
]
}
navmenu
]
}
Before it looks like this:
mudDrawer() {
open' isOpen
elevation 25
variant DrawerVariant.Persistent
childContent [
mudDrawerHeader() {
linkToIndex true
childContent [
mudText() {
color Color.Primary
typo Typo.h5
childContentStr "Have fun ✌"
}
]
}
navmenu
]
CAST
}
For more information please check the repo.
Recommend
-
94
Hex DSL ...
-
89
Kotlin is a great general purpose language, but it also supports building domain-specific languages, or DSLs.
-
57
kotlin dsl learn
-
97
Kotlin DSL 把 Kotlin 的语法糖演绎得淋漓尽致,这些语法糖可谓好吃、好看又好玩,但是,仅痴迷于语法糖只会对语言的理解游离于表面,了解其实现原理,是我们阅读优秀源码、设计整洁代码和理解编程语言的必经之路,本文我们通过 DSL 来感受 Kotlin 之美。 理解 DSL...
-
45
-
7
Improve overall user experience in using F# with expression-based ORMs #10523
-
5
#Bolero#Blazor#Fsharp2021/07/01 11:04:44Giraffe style routing for Fun.BlazorBefore I experimented Feliz.Router with Fun.Blazor and find some inconvenience so this time I tried to use...
-
12
Experiment on computation expression style for Blazor with F#Before I use Feliz style for the auto generated dsl but find its a little redundant, so let`s try CE styleBefore I use Feliz style for the auto generated DSL but fin...
-
7
Saturday, December 18, 2021 [ADVENT 2021] Initial quick dive into F# 6 task computation expression Hi my blog readers!First, personal announcement related to this blog:If you fol...
-
4
TL;DR: F# 6 makes it possible to use computation expressions for HTML and have much better performance than the current list-based syntax. It could look like this: div { text "Welcome to " a {...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK