The difference between `let f = fun a & # 8594; a-1` and `let fa = a-1` in F #

In F #, is there any difference between let f = fun a -> a-1 and let fa = a-1 ? As far as I can see, the latter is just syntactic sugar for the former. Is it correct?

I look specifically at the semantic differences here, and not at the differences in how a particular compiler handles two cases.

+6
source share
3 answers

(fun a -> ...) is just an anonymous function. F # differs from C # in that the functions are first class citizens, so when you associate an anonymous function with f , it will give f signature like val f : int -> int (since a output as int32 ), just like if you bound a normal named function, as in your second example. You can prove that they are semantically identical by running the examples in F # interactive.

Edit: anonymous functions even support shared files, which are usually output, but can be made explicit:

let f<'T> = (fun (x: 'T) -> x)

Edit 2: From the specification specification of F # 3.1 (found here) :

A value definition is considered a function definition if its immediate right side is an anonymous function, as in this example: let f = (fun w -> x + w)

Setting the obvious syntax error aside, this suggests that binding the value of a function (i.e. an anonymous function) to an identifier is literally equivalent to defining a normal function. It goes on to say that this equivalence is "the main product of functional programming," so you can be sure that this will not change in the future.

For general purposes, F # considers function definitions and function values ​​(i.e., delegation instances, anonymous functions) as equal at design time, but when it needs to raise the function definition to the function value, it will use the FSharpFunc delegate type as a compiled type. This applies to all functions of a higher order, for example, in Array, List, Seq, etc. arrays, since there is no real way to use the CIL method as a function value, as you can, with delegates. Everything else looks exactly as you would expect compiled C # or VB.NET - these are just delegates for which F # uses FSharpFunc.

Edit 3: I cannot add comments yet, but regarding Thomas's answer, the F # compiler does not know how to generalize the expression let h = (); fun a -> a let h = (); fun a -> a , but it will accept it if you add annotations like yourself, for example with the Nikon id example.

Edit 4: Here's a very rough picture of how F # compiles. Note that Thomas’s example, an expression of a sequence, as he called it, turns into FSharpFunc, whereas an equivalent function without (); becomes the actual CIL method. This is what the F # specification said above. Also, when you use the regular CIL method as a value, with a partial application or otherwise, the compiler will make FSharpFunc present it as a closure. enter image description here

+7
source

As already mentioned, the simple answer is that defining a function using let foo x = ... and using let foo = ... may give you different results (due to the limitation of values), but in this case this does not happen since the compiler is smart enough to know that it can relate to let foo = fun x -> .. like let foo x = ...

Now, if you want to see more detailed information, try the following definitions:

 let fa = a // Function 'a -> 'a let g = fun a -> a // Function 'a -> 'a let h = (); fun a -> a // error FS0030: Value restriction 

Thus, the compiler treats the function defined with let foo = fun x -> ... as a normal function if there is nothing but the code that creates and returns the function. If you do anything before (even if it just ignores the value of one), then there is a difference.

If you want to know more, then section 14.6.7. A generalization in the F # 3.0 specification lists all the generalized values ​​(i.e. the expressions for which it works above):

The following expressions are generalized:

  • Functional expression
  • Interface expression implementation
  • Delegate expression
  • The expression "let", in which both the right-hand side of the definition and the body of the expression are generalizable
  • The expression "let rec" in which the right-hand sides of all definitions and the body of the expression are generalizable.
  • The correct expression, all elements of which are generalized ο‚·
  • Expression of a record, all elements of which are generalizable, where the record does not contain mutable fields ο‚·
  • A case union expression whose arguments are all generalized
  • An exception expression with all arguments generalizable
  • An empty array expression
  • Constant expression
  • An application of a type function that has the GeneralizableValue attribute.

The first point is important - that the expression of the function (for example, fun x -> x ) is generalizable; Expression of a sequence (for example, (); fun x -> x ) is not generalizable and therefore behaves differently from a simple function.

+7
source

The examples you presented are semantically the same, but the F # compiler has everything related to it.

Let's look at another (general) function:

 // val singleton1 : x:'a -> List<'a> let singleton1 x = [x] // val singleton2 : x:'a -> List<'a> let singleton2 = fun x -> [x] 

As you can see, the signatures are the same. But if you think about it, these two should not be the same: the first is a true function (compiled into a .NET method), but the second is just a value containing a function (delegate or Func in C #). But there are no common values ​​in the .NET runtime. This only works because the F # compiler is smart enough to make a singleton2 function too.

You can see what I mean here:

 let singleton3 = id >> fun x -> [x] 

Now we have outsmarted the F # compiler and this will not compile due to the Cost Limit (scroll down to the topic), although it should be semantically the same as singleton2 .

So to summarize: from a semantic point of view, your definitions are the same, but due to limitations in the .NET runtime, the F # compiler needs to do some extra work to enable this.


Another difference that I just remembered is that only functions can be marked inline :

 let inline fa = a-1 // OK let inline f = fun a -> a-1 // Error 
+5
source

Source: https://habr.com/ru/post/972365/


All Articles