I suggest we ...

Pure Functions - "pure" keyword

I've been thinking about this one for a while.

At the moment, it's pretty easy to make a pure function and as a long as you follow a few rules, you should be fine. There is just no guarantee. A way for the F# compiler to enforce a function to be "pure" and not allow any side effects/mutations ever would ensure that guarantee. A "pure" function will have more constraints. This may also allow for more aggressive optimizations.

A challenge with this is how do we handle existing functions that are referentially transparent and types that we know are immutable. My best shot at this is only allow the F# compiler to make exceptions to functions/types to be allowed in "pure" functions. An example would be the "string" type and its functions.

116 votes
Vote
Sign in
(thinking…)
Sign in with: facebook google
Signed in as (Sign out)
You have left! (?) (thinking…)
Will Smith shared this idea  ·   ·  Flag idea as inappropriate…  ·  Admin →

21 comments

Sign in
(thinking…)
Sign in with: facebook google
Signed in as (Sign out)
Submitting...
  • Bartosz Sypytkowski commented  ·   ·  Flag as inappropriate

    A pure function could have it's own type i.e. PureFunc<,> inheriting from FSharpFunc<,>. They could have potentially their own optimized implementation, but I think that the greates value added here is the potential for the compiler to optimize chained pure function calls i.e:

    1. Multiple calls to pure methods doing record transformations like `mutate (fun x -> { x with A = a }) |> mutate (fun x -> { x with B = b })` could be safely reduced to `mutate (fun x -> { x with A = a; B = b }) form.
    2. Sequenced calls like `array |> Array.filter fn |> Array.map fn` could also be optimized to reduce allocations in every step, since we know, we're side-effect free.

    I think the exceptions like in case of division operator are not problem here, as long as we don't try to catch them and operate on the exception, which by itself is not a "pure" function.

  • Yemi Bedu commented  ·   ·  Flag as inappropriate

    Hello,
    So the prior comment by Don is what may bring this back to life. There is :
    https://fslang.uservoice.com/forums/245727-f-language/suggestions/5663074-enable-a-compiler-warning-when-a-recursive-algorit

    That we know can benefit because of what the CLR provides. So in the pure (except exceptions) case, what can be done to help transform code. Do we get any benefit from auto in lining of a pure function? Will the compiler warning tell us "unable to infer purity of function X."?

    Since we can have divide by zero and other effects, this is something that would only scratch the surface of utility about reasoning about our programs. In feels like in sits in a boat of "Code Quality Contracts". Pure, TailRecursive, NonNullable, Immutable can all be implemented in basic ways to help smelly code stand out. we already have immutability and many non-null assignable types. The new tail recursion check that is planned is just as beneficial and not *needed*.

    I think having only functions instead of modules marked pure is good to not interfere is notions about the value bindings. The functions marked are only indicating that are only calling internal functions of functions exposed as pure.

    There can be levels to it like in F*(fstar) where it is total or not or that it is *expecting* a function to be pure. The latter can be seen as *clean*. so you mark List.map as clean and the whole function will be clean. if it is able to obtain an internal pure status, all use sites will also be upgraded to pure. Thank you. Good day.

  • Don Syme commented  ·   ·  Flag as inappropriate

    The proposal doesn't mention what happens in higher order cases. For example, can you constrain an input function to be pure?

    Also it's not actually clear what additional optimizations we would actually implement (rather than think about implementing) if "pure" were added.....

  • knocte commented  ·   ·  Flag as inappropriate

    I've realized that there could be a simpler way to achieve this: in the same way C# requires you to use an -unsafe flag to call the compiler when you use the unsafe keyword in your project, F# could require a -mutable flag when you use the mutable keyword in any shared member (mutable keywords for variables inside a method, for example, would be allowed, as they are not shared).

    This is maybe not a full solution w.r.t. purity/impurity handling (because you could still have impurity via IO without mutable members), but it would be a first step towards it.

  • Schalk Dormehl commented  ·   ·  Flag as inappropriate

    The true value of the fuctional revolution has been to seperate commands and queries from one another. The problem is that the idea has been extrapolated beyond it's real usefulness, IE things like Haskell, where there are no commands, only functions but now commands need to be simulated.

    Ironically good command / query seperation has existing in SQL for a long, long time. T-SQL functions do not have side effects, you can't even call Random or Print.

    The introduction of a Command keyword and a "pure" keyword would be epic. I think that the best method for implementing the pure keyword is to have it as an extension to the language initially. It would simply not be allowed to do any re-assignements to mutables and it will not be allowed to call a command. It may be necessary to go one step further than that and not allow it to refer to any mutable variables either to ensure memiozation.

    PS Typescrypt has added the let keyword. I think the chances are best to introduce a pure fuction syntax into that.

  • Don Syme commented  ·   ·  Flag as inappropriate

    The ramifications on a type system to track effects and/or purity are substantial, and considerably more is needed than declaring and checking named functions as pure. For example:

    - The integration with higher-order features is very difficult. "List.map pureFunction" is also pure, but the type system won't know this. "Seq.map pureFunction" is pure if the input enumerable is pure when enumeration is performed, but the type system won't know this

    - Other effects besides purity (the "zero effect" quickly become of interest, including termination, exceptions, read/write effects, IO effects etc.

    - The space of possible inferred types for code becomes large (with no "best type" guaranteed) and the inferred types themselves are large and subtle

    There are human-factor considerations to take into account as well - "can a beginner understand the type of List.map" and "when and where do we show effect annotations in error messages?" and "can a team mixing beginners and advanced experts make good use of this feature?"

    For F# the stable approach is has so far been to push this off to FSharpLint-style tooling. The F# compiler service exposure of typed expression trees is relevant here.

  • amazingant commented  ·   ·  Flag as inappropriate

    While I agree it would have been nice when F# was originally being developed, Microsoft doesn't really do breaking changes like that. Even as just a compiler warning, if it's an opt-out I'll end up telling management that "Microsoft changed this behavior in the last release, so I need to rewrite some code." Nobody will care that it improves the code quality or that it can be turned off, they'll just care that Microsoft "broke" something.

    If this is added as a "pure" keyword, or some other opt-in, I can tell management that "I'm turning on this new feature so that I write better code; it will probably tell me some old code isn't as good as it could be." While it means it's something they can tell me not to do because time constraints, I could still start sprinkling the pure keyword into code as things get added or rewritten.

  • Jannick B commented  ·   ·  Flag as inappropriate

    I'd rather have an impure keyword added to anything that and let pure be the default

  • Ovidiu Deac commented  ·   ·  Flag as inappropriate

    As knocte suggested I think it's better to have impure, in the same way we have mutable. Then you should be able to disable the warning for backwards compatibility.

  • Alfonso Garcia-Caro commented  ·   ·  Flag as inappropriate

    I like the idea, but I also think it would be more useful to mark whole modules or libraries as pure rather than single functions.

  • amazingant commented  ·   ·  Flag as inappropriate

    I'd definitely prefer the compiler directive as Will put it. Coming from VB development, it's nice to be able to turn off Option Strict for the few files in a project that haven't been cleaned up yet.

    Following that train of thought, for libraries and so forth, perhaps being able to set this value for the project as a whole? This would mean taking the opposite approach to the OP and more towards what I have to do with VB's Option Strict, namely telling the compiler "please consider this entire project to be made of pure functions...except this file"

  • Anonymous commented  ·   ·  Flag as inappropriate

    Microsoft Research's Koka programming language infers this quality as part of the type. They take it further with Total functions (pure functions that do not diverge or throw). It seems like a logical next step to have the 'effectfulness' be a part of the type, and for it to naturally be inferred, although as the paper on it mentions this adds some complexity when composing.
    If you can infer 'purity' or 'totalness' 'pure' or 'impure' could be an optional keyword on the type.

  • Will Smith commented  ·   ·  Flag as inappropriate

    Maybe a better way to handle this would be to have a compiler directive:

    #pure

    This will make the file you are in only able to use pure functions and immutable types. This would get rid of having to have "pure" being used with functions or modules as well as using the [<Pure>] attribute. Should be a fair compromise. Thinking about it, I think a compiler directive is the cleanest way.

  • knocte commented  ·   ·  Flag as inappropriate

    > This means that all existing code would not compile and/or have warnings all over of the place

    You would only pay this price if you upgrade to F# v.Next

  • Will Smith commented  ·   ·  Flag as inappropriate

    This means that all existing code would not compile and/or have warnings all over of the place. :(

    Maybe, "pure" can be on functions like this:

    let pure f x = x + 1

    --
    but we could also have it on modules:

    module pure internal BestModule

    --
    That way, anything inside the module would only allow pure functions. Though, it gets more complicated as modules can contain classes, records, structs, etc.

  • knocte commented  ·   ·  Flag as inappropriate

    IMHO, in the same way we have a 'mutable' keyword, we shouldn't have a 'pure' keyword, but an 'impure' one! Always default to the best outcome...

    This way, all functions should be assumed pure, and when one is not, the compiler should generate a warning if it doesn't contain the "impure" keyword.

  • Will Smith commented  ·   ·  Flag as inappropriate

    Not fully against using the [<Pure>] attribute; however, when writing pure functions, I would hate to have to type out "[<Pure>]" over every method on top of "let".

    Imagine this everytime:

    [<Pure>]
    let addOne x = x + 1

    [<Pure>]
    let addTwo x = x + 2

    vs.

    let pure addOne x = x + 1

    let pure addTwo x = x + 2

    Using the keyword makes creating the pure functions a natural part of the language. Using the attribute just feels like a fix to get the desired behavior.

    Now, it's ok to decorate the compiled pure function with the [<Pure>] attribute.

  • Will Smith commented  ·   ·  Flag as inappropriate

    Richard,

    Pure functions give you an absolute guarantee that a function does not have any side effects. Can be very useful if you need to run computations in parallel. We can still do this, but there is no guarantee.

    I'm not sure how I feel with using immutable properties, I think I would only want to use immutable records/structs with no fields marked as mutable. But, maybe it does make sense to use only immutable properties. I think it's definitely worth exploring this idea. :D

← Previous 1

F# Language

Feedback and Knowledge Base