-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a String-focused Computation Expression Syntax #1149
Comments
I'm not sure everyone would find this syntax useful enough to be worth the increased complexity. Why not write a library instead? |
I'm sure not everyone would find this syntax useful enough. :) From a users point of view, the proposal would decrease complexity and simplify building strings in many cases. This following code makes this clearer, which uses string interpolation to render a list of elements: let names = [ "Iris"; "Magda" ]
$"""<grid>{names |> List.map (fun name -> $"<row>User: {name}</row>") |> String.concat ""}</grid>""" This gives: As known from CEs, let names = [ "Iris"; "Magda" ]
$"""<grid>{for name in names do}<row>User: {name}</row>{done}</grid>""" I assert that the 2nd representation is a simpler form than the initial one, where "simpler" means "easier to write and read", and that the way of thinking in the 2nd case is very well known from computation expressions. This gets even clearer when the level of nesting increases, or when the template becomes more complex, e.g. by line breaks or growth in size. In general, this proposal is about extending string interpolations from "concatenating strings and isolated (nested) expressions" to "yielding strings from within a computational context (=CE)". Why one would want to have this in addition to having just current string interpolation? Propably for the same reasons for why one would want to have CEs in the language in addition to
This is a very general question. A library is of course possible, and could be useful, but there are disadvantages, like: No proof of correct tempalte usage by the compiler, higher burden of usage, not idiomatic, no intellisense, etc. Or, to answer the question with another question, here it comes: Why is this: $"Hello, {user.name}, how are you?" better than this: $"Hello, " + user.name + ", how are you?" The answer is:
|
Pardon. By "complexity", I meant "how many syntaxes you have to learn", not "how complex your syntax is for any particular use case". I'm not sure it's enough of an improvement over the |
Methods of string generation for comparison: A plausible string CE using string interpolation: stringBuffer {
$"""
Good day, %s{customer.name}! Here are your orders:
===
"""
for order in customer.orders do
let status = if order.isDelivered then "You got it." else "ON OUR WAY!"
$"""
ID: %i{order.id}
Quantity: %i{order.qty}
Status: %s{status}
"""
"""
End of report.
"""
} The proposed CE: $"""
Good day, {customer.name}! Here are your orders:
===
{for order in customer.orders do}
ID: {order.id}
Quantity: {order.qty}
Status: {if order.isDelivered then}You got it.{else}ON OUR WAY!{end}
{done}
End of report.
""" With no CE: module order =
let report order =
let status =
if order.isDelivered then
"You got it."
else
"ON OUR WAY!"
$"
ID: %i{order.id}
Quantity: %i{order.qty}
Status: %s{status}"
module customer =
let report customer =
let orders = System.String.Join("\n", List.map order.report customer.orders)
$"
Good day, %s{customer.name}! Here are your orders:
===
%s{orders}
End of report." |
Thanks @the-not-mad-psychologist , I understand, and questioning new ways of expressing things that can already be expressed in the language is important. I guess that the majority of suggestions is about new ways for representing things, rather than enabling things one could not express before (which is propably hard to find in a complete programming language). In this particular case, it's true that this is about a new way of representing string building that can currently be achieved in other ways. In my opinion, it is worth the effort for this case. A note on the "learning things" part: I think one doesn't have to learn a complete new feature, because it's possible to think of the proposed syntax as an extension of string interpolations - it's an addition. It allows a developer to evolve if there is a need or not. Also, from a conceptual view, the idea is well-known from CEs. If one understand them, the transition to the proposal is a low barrier, since no new concepts have to be learned. I'm also not absolutely sure about those things; they are suggestions. In this case, the proposal is driven by my latest experiences that involved a lot of text templating tasks. The quoted statement from the string interpolation RFC ("...improves readability and reduces errors by staying visually closer to the end result.") was my motivation for more complex templates involing loops and conditionals. |
@the-not-mad-psychologist I took the liberty of removing the I think suggestions to make interpolated strings be even more powerful as comprehensions do make sense. However for the above the lack of indentation for the One option might be to allow interpolands to include control flow and nested interpolations, e.g. e.g. $"""...{if condition then string-generating-expr}..."""
$"""...{while condition do string-generating-expr}..."""
$"""...{for v in coll do string-generating-expr}..."""
$"""...{string-generating-expr1; string-generating-expr2}..."""
$"""...{match input with pat -> string-generating-expr}..."""
$"""...{let v = rhs in string-generating-expr}...""" e.g. $"""hello{if condition then
$"world at {DateTime.Now}"
if otherCondition then
$"there at {DateTimeOffset.Now}"
while condition do
$"one line"
} and some more here""" Leading to this: $"""
Good day, {customer.name}! Here are your orders:
===
{for order in customer.orders do $"
ID: {order.id}
Quantity: {order.qty}
Status: {if order.isDelivered then "You got it." else "ON OUR WAY!" "}
End of report.
""" Needs more nesting of interpolations than currently allowed by F# and may not be a coherent design. |
Also a question if you could only generate strings - currently interpolands with no let coll = [0..100]
$"""The integers are {for x in coll do x; ","} when comma-separated"""```
$"""The integers are {for x in coll do x; ";"} when semicolon-separated"""``` However there are just some comprehension patterns that are very specific to common cases particular to strings which don't drop out that well in generative comprehensions. For example the above adds a trailing ";" and ",", and if you want to avoid this the options aren't so great: $"""The integers are {
let mutable first = true
for x in coll do
if not first then ","
first <- false
x} when comma-separated""" And this is neither obvious nor particularly elegant:
And maybe you really want some kind of CE-custom-operator-like-thing:
|
I think that the power of interpolations come from the pattern "interrupt string, then continue string". // "holes" as of today (with for, if, etc. not as control construct)
$"""...{string-generating-expr}..."""
// control constructs
// 'do', '->', 'in', 'then' are omitted here
$"""...{for x in xs{...}..."""
$"""...{match x with pat1{...{|pat2{...}..."""
$"""...{let x = 10{...}..."""
$"""...{while cond{...}..."""
$"""...{if cond{...}..."""
$"""...{if cond{...{else{...}..."""
$"""...{custom-operation{...}..."""
// not needed:
$"""...{string-generating-expr1; string-generating-expr2}..."""
// because it can be expressed with
$"""...{string-generating-expr1}{string-generating-expr2}...""" Control constructs can be used by string "broken" by Examples / Alternatives This: $"""The integers are {for x in coll do x; ","} when comma-separated"""
$"""The integers are {for x in coll do x; ";"} when semicolon-separated""" could be this: $"""The integers are {for x in coll{{x},} when comma-separated"""
$"""The integers are {for x in coll{{x};} when semicolon-separated""" Formatting can be applied in the usual way: $"""The integers are {for x in coll{%i{x},} when comma-separated"""
$"""The integers are {for x in coll{%i{x};} when semicolon-separated""" The initial example: $"""
Good day, {customer.name}! Here are your orders:
===
{for order in customer.orders{
ID: {order.id}
Quantity: {order.qty}
Status: {if order.isDelivered{You got it.{else{ON OUR WAY!}
}
""" Comprehension patterns For a way of expressing patterns like joining, edge case handling (...whenEmpty), etc., CSS pseudo selectors come in my mind, which handle a similar problem in an acceptable way. Expressing patterns could be achieved by small descriptors which are provided alongside the seq expression in 'for'. This is the idea: type PatternDescriptor =
| Join of sep:string
// others; find a way of mixing patterns in a meaningful way
type TextBuilder () =
member _.Yield(txt: string) = txt
member _.Combine(f: string, g: string) = f + g
member _.Delay(f: unit -> string) = f()
member _.Run(f: string) = f
member _.Zero() = ""
member inline _.For((xs: #seq<'a>, Join sep), f: 'a -> string) =
xs |> Seq.map f |> String.concat sep
member this.For(xs: 'a seq, f: 'a -> string) =
this.For((xs, Join ""), f)
let text = TextBuilder()
// using CE
text {
"Integers: "
for x in [1..3], Join("; ") do
$"Number-%i{x}"
}
// using string interpolations
$"""Integers: {for x in [1..3], Join("; "){Number-%i{x}}"""
// Gives:
// "Integers: Number-1; Number-2; Number-3" |
I must protest. The very use of brackets implies that each open-bracket is to be matched by a close-bracket. If we want a continuation operator, we should use a different symbol. |
What about this:
That would look pretty close to C#'s Razor: $"""Integers: @for x in [1..3]{Number-%i{x}}""" In that case, it wouldn't be possible to use CEs in a control flow expression. Since it's about convenience for practical string building cases and not obscure constructs (meaning: Starting CEs inside string interpolations will not be supported), that might be ok. |
I propose a string-focussed syntax for computation expressions.
Example:
The
result
is:The above can be seen as:
if
andfor
.Looking from a CE point of view, this proposal is about a syntax sugar for the
StringBuffer
CE, which is discussed here. A computation expressed in the proposed string-focussed syntax can be transpiled into the current CE syntax usingStringBuffer
:Notes
{done}
and{end}
are used.Current alternatives:
StringBuffer
CEBenefits:
Extra information
Estimated cost (XS, S, M, L, XL, XXL): L to XL
Related suggestions: #775
Please tick this by placing a cross in the box:
Please tick all that apply:
For Readers
If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.
The text was updated successfully, but these errors were encountered: