fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features
345 stars 21 forks source link

suggest: `valueof` operator to eval expression at compile time #804

Closed xp44mm closed 1 year ago

xp44mm commented 4 years ago

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:

let name = "abc"
let length = 3 // value of name.length

maybe, we can do this:

let name = "abc"
let length = valueof name.Length

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:

purity function that take arguments that are all constant return constant result.

abelbraaksma commented 4 years 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.

xp44mm commented 4 years ago

@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.

abelbraaksma commented 4 years ago

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.

xp44mm commented 4 years ago

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())
yatli commented 4 years ago

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.

xp44mm commented 4 years ago

@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 .

Happypig375 commented 4 years ago
> 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.

yatli commented 4 years ago

@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. :)

Happypig375 commented 4 years ago

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.

yatli commented 4 years ago

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.

Happypig375 commented 4 years ago

https://github.com/fsharp/fslang-suggestions/issues/804#issuecomment-550128725 There are signs of arrays-as-literals too.

yatli commented 4 years ago

What do you mean? Arrays can only be static readonly at best.

yatli commented 4 years ago

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.

yatli commented 4 years ago

@xp44mm recommend to update the title to something more truly reflects the intent. :)

yatli commented 4 years ago

One of the C# discussions on this topic:

https://github.com/dotnet/roslyn/issues/15079

xp44mm commented 4 years ago

@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
Happypig375 commented 4 years ago

Isn't that the current state? The let binding is evaluated only once. You can put it in global scope to ensure that.

xp44mm commented 4 years ago

@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.

Happypig375 commented 4 years ago

How will you know that Length will not modify state?

xp44mm commented 4 years ago

@Happypig375 abc is a literal "abc", so it Length property abc.Length should be literal 3 also. this issue just for performence.

Happypig375 commented 4 years ago
> 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.

xp44mm commented 4 years ago

@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.

Happypig375 commented 4 years ago

You cannot determine whether string.Length is a pure function. There is no indication of this.

xp44mm commented 4 years ago

no effect itself, && no call impure function, is pure function.

Happypig375 commented 4 years ago

How can this be checked?

xp44mm commented 4 years ago

first, provide some basic pure function as seed. and no effect itself, && just call known pure function, is pure function.

Happypig375 commented 4 years ago

some basic pure function as seed

Which?

xp44mm commented 4 years ago

operator, arith +-*/, Math operator, relation operator, string operator, compare operator. maybe so. Excel function most in.

Happypig375 commented 4 years ago

Operators only?

xp44mm commented 4 years ago

operator is safe collection. other function may be pure function. can manual mark also:

[<TranspileToLiteral>]
let len = abc.Length // -> let len = 3
Happypig375 commented 4 years ago

https://sharplab.io/#v2:CYLg1APgAgTAjAWAFBQMwAJboMLoN7LpGYZQAs6AsgBQCU+hxTAxgPYB2AzgC7oCW7XgA10AXnSoA3IyZE2XXsFYBXAEYAbAKboAmmPQBWaUlnF5PdErVb0AQX16A9OhjHTcjhasbtAIX32AFQSbu7miio+OPoiAGQuoabhlpE2ACIB6M6+ibLJ3jYAovpwAAzoAKToabksnhHW2gBi+tQC3LS4ADxd6G2CtDq1ZvX8gugA4vpSWY4ARACGqsxzAHQAMprsAObcABbDRFBwAJzUQrSHmKfUOpcyssdntvcm7k/Uvq/u12fY3+8bmkAaYPoUQY8bk0IUwPhMIQBfZAIoA

Operators can be interpreted as constant, evidenced by the C# compiler. Even so, it cannot infer string.Length as constant.

xp44mm commented 4 years ago

i admired you, The length of "abc" is 3, 3 is literal, literal is constant, any wrong?

Happypig375 commented 4 years ago

Because string.Length is a member on string, not an operator.

Happypig375 commented 4 years ago

If string.Length is interpreted as constant, then we will have to keep a whitelist on .NET BCL types. Not sure this is worthwhile.

Happypig375 commented 4 years ago

If this is just an optimization, then this is a micro-optimization and you should focus on better places to optimize.

xp44mm commented 4 years ago

if too dificult to determine pure function. just transpile the manual mark [<TranspileToLiteral>], that is very good also.

Happypig375 commented 4 years ago

string.Length is not written in F#. You cannot mark it as [<TranspileToLiteral>] in F# code.

xp44mm commented 4 years ago
[<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.

Happypig375 commented 4 years ago

This duplicates [<Literal>].

xp44mm commented 4 years ago

[<Literal>] no work. can not to compile. it must mark equal right side is real literal. it use for pattern match.

Happypig375 commented 4 years ago

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.

xp44mm commented 4 years ago

right! a different attribute.

Happypig375 commented 4 years ago

Back on topic, how would you classify "pure functions"? If string.Length is one, then what is the rule?

xp44mm commented 4 years ago

length is pure function, because it is char array's count, it call +. and without effect itself.

Happypig375 commented 4 years ago

How can the F# compiler know this?

xp44mm commented 4 years ago

array.map, array.reduce, is pure function.

yatli commented 4 years ago
Happypig375 commented 4 years ago

What will be in the whitelist?

Happypig375 commented 4 years ago

array.map, array.reduce, is pure function.

Will this be the result of attributes or transitivity?

yatli commented 4 years ago

I don't think Array.map is constexpr at all.