Closed xp44mm closed 1 year ago
You can already do this, Length
is a valid property on a string, but with a capital L.
let name = "abc"
let length = name.Length // gives 3
And because it's a let binding, if it's part of a module, it's only calculated once.
@abelbraaksma
if the length
lie in a function that will be call n times, then length
will be calculated n times.
but nameof
or valueof
will be calculated once at compile time. it's will be calculated zero times at any runtime.
Ah, you mean you want some way of writing expressions that evaluate to literals (aka constants), in other words, you want to introduce a macro language, something like C++ has?
Maybe you can clarify that in your original text,and add use cases and more examples that are resolved (take a look at the template). It may also help to emphasize the constant nature, like: let <Literal> len = valueof x.Length
or something.
It could be powerful, but I wouldn't be surprised if there's already a suggestion hanging around with a similar idea.
fsharp should be able to inference that some values are constants actually in logical. it will be reduced a lot of boilerplate design pattern. for example, memoize, curray ...
there are lot of use cases, i can proivde a case. excel sheet have address of range represent A1
, and parse it need some constant. so:
///excel sheet max column address
let maxLetters = "XFD" //letter maxColumn
/// [|x;d;f|] index of a..z
let maxValues = [|23; 5; 3|]
/// d + f * 26 + x * 26 * 26
let maxColumns = 16384 //position maxLetters
/// xfd.length = 3
let maxLength = 3
Debug.Assert((maxLength = maxLetters.Length), "maxLength", maxLetters.Length.ToString())
In F#, const variables are marked with [<Literal>]
.
To my knowledge there's only partial support for C++ constexpr
-style values, because objects like arrays are always run-time objects, and cannot be declared [<Literal>]
Adding support for real constexpr
-style compile-time evaluation would be a big effort.
@yatli
[<Literal>]
并不是重点,有更好,没有也无伤大雅。
it's no important, infer out [<Literal>]
not very much improvement.
编译时推算全局不变量确实是一个艰巨的任务。能够推算一些简单的计算,比如1+1
,就很高兴了。
compile-time evaluation some let
binding as global immutable contant indeed is a big effort. if be able to recongnize very simple, 1+1
, that it is very good .
> let (+) x y = printfn "Hi~"; x - y
let a = 1 + 1
let b = 3 + 2;;
Hi~
Hi~
val ( + ) : x:int -> y:int -> int
val a : int = 0
val b : int = 1
> let [<Literal>] a' = 1 + 1;;
let [<Literal>] a' = 1 + 1
---------------------^^^^^
stdin(13,22): error FS0267: This is not a valid constant expression or custom attribute value
[<Literal>]
cannot be inferred here.
@Happypig375
I think @xp44mm would want something like this:
[<ConstExpr>]
let ( + ) x y = x - y
[<Literal>]
let a = 1 + 1
In a constexpr function, all bindings need to be literal.
As long as we keep printf
not [<Literal>]
we will be fine. :)
What about "".Length
? Should that be made a [<Literal>]
? That is a part of the .NET BCL, we would have to make a whitelist here.
Strings are a little bit special already because of the private implementations. A string is an object, has a ton of constructors, and yet it's not mutable as arrays/lists, and could be literals.
https://github.com/fsharp/fslang-suggestions/issues/804#issuecomment-550128725 There are signs of arrays-as-literals too.
What do you mean? Arrays can only be static readonly
at best.
If we want to support constexpr for any type, a potential way is to keep the initialization bytecode in the assembly -- so when another assembly references it, it can run the initializer again at compile time..
Sounds like, this can be done in a language neutral way, something like an IL injector.
@xp44mm recommend to update the title to something more truly reflects the intent. :)
One of the C# discussions on this topic:
@yatli @Happypig375
i just want to F# recongnize some let
binding virtually is literal. is NO relationship to [<Literal>]
let abc = "abc"
let chars= abc.ToCharArray()
let len = abc.Length
the compiler will transpile it to as:
let abc = "abc"
let chars= [|'a';'b';'c'|]
let len = 3
Isn't that the current state? The let binding is evaluated only once. You can put it in global scope to ensure that.
@Happypig375
no! compiler will replace the source code with literal. the len
in the file .dll or .exe is 3
, instead of abc.Lengh
, it need not to calculate even once.
How will you know that Length
will not modify state?
@Happypig375
abc is a literal "abc"
, so it Length property abc.Length
should be literal 3
also. this issue just for performence.
> let r = System.Random()
type System.String with
member _.Length' = r.Next()
let a = "123".Length'
let b = "123".Length';;
val r : System.Random
type String with
member Length' : int
val a : int = 20296113
val b : int = 36580259
abc is a literal "abc"
, but its Length'
property abc.Length'
is not constant.
@Happypig375
abc.Length
property is constant ==| abc.AnyProperty
is constant.
abc.SomeProperty
is not constant ==| abc.Length
is not constant.
the property or function need is pure function. purity function that take arguments that are all constant return constant result.
You cannot determine whether string.Length
is a pure function. There is no indication of this.
no effect itself, && no call impure function, is pure function.
How can this be checked?
first, provide some basic pure function as seed. and no effect itself, && just call known pure function, is pure function.
some basic pure function as seed
Which?
operator, arith +-*/, Math operator, relation operator, string operator, compare operator. maybe so. Excel function most in.
Operators only?
operator is safe collection. other function may be pure function. can manual mark also:
[<TranspileToLiteral>]
let len = abc.Length // -> let len = 3
Operators can be interpreted as constant, evidenced by the C# compiler. Even so, it cannot infer string.Length
as constant.
i admired you, The length of "abc" is 3, 3 is literal, literal is constant, any wrong?
Because string.Length
is a member on string, not an operator.
If string.Length
is interpreted as constant, then we will have to keep a whitelist on .NET BCL types. Not sure this is worthwhile.
If this is just an optimization, then this is a micro-optimization and you should focus on better places to optimize.
if too dificult to determine pure function. just transpile the manual mark [<TranspileToLiteral>]
, that is very good also.
string.Length
is not written in F#. You cannot mark it as [<TranspileToLiteral>]
in F# code.
[<TranspileToLiteral>]
let len = abc.Length // -> let len = 3
just mark let binding left side , whatever equal sign right side is. right side of equal sign will be eval at compile time, tranpile to literal.
This duplicates [<Literal>]
.
[<Literal>]
no work. can not to compile. it must mark equal right side is real literal. it use for pattern match.
It is not used just for pattern matching. Active patterns can do much more. Instead, it reduces duplicate information in attributes so the value can be changed easily. Anyways, it is not for optimizations.
right! a different attribute.
Back on topic, how would you classify "pure functions"? If string.Length
is one, then what is the rule?
length is pure function, because it is char array's count, it call +. and without effect itself.
How can the F# compiler know this?
array.map, array.reduce, is pure function.
[<ConstExpr>]
, or white-listed)What will be in the whitelist?
array.map, array.reduce, is pure function.
Will this be the result of attributes or transitivity?
I don't think Array.map
is constexpr at all.
fsharp have added
nameof
operator in recent version. it is just a syntax sugar. but people like it. I propose we should be to do further more.for example:
maybe, we can do this:
this code will be translated into previous code at compile time. it will reduce people a lot of typos. and more readable code.
maybe, we can get an inference: