How do you curry the 2nd (or 3rd, 4th, …) parameter in F# or any functional language?

前端 未结 5 1498
予麋鹿
予麋鹿 2021-02-19 06:58

I\'m just starting up with F# and see how you can use currying to pre-load the 1st parameter to a function. But how would one do it with the 2nd, 3rd, or whatever other paramet

相关标签:
5条回答
  • 2021-02-19 07:41

    Typically you just use a lambda:

    fun x y z -> f x y 42
    

    is a function like 'f' but with the third parameter bound to 42.

    You can also use combinators (like someone mentioned Haskell's "flip" in a comment), which reorder arguments, but I sometimes find that confusing.

    Note that most curried functions are written so that the argument-most-likely-to-be-partially-applied comes first.

    F# has named parameters for methods (not let-bound function values), but the names apply to 'tupled' parameters. Named curried parameters do not make much sense; if I have a two-argument curried function 'f', I would expect that given

    let g = f
    let h x y = f x y
    

    then 'g' or 'h' would be substitutable for 'f', but 'named' parameters make this not necessarily true. That is to say, 'named parameters' can interact poorly with other aspects of the language design, and I personally don't know of a good design offhand for 'named parameters' that interacts well with 'first class curried function values'.

    0 讨论(0)
  • 2021-02-19 07:46

    In Python, you can use functools.partial, or a lambda. Python has named arguments. functools.partial can be used to specify the first positional arguments as well as any named argument.

    from functools import partial
    
    def foo(a, b, bar=None):
        ...
    
    f = partial(foo, bar='wzzz') # f(1, 2) ~ foo(1, 2, bar='wzzz')
    f2 = partial(foo, 3)         # f2(5) ~ foo(3, 5)
    
    f3 = lambda a: foo(a, 7)     # f3(9) ~ foo(9, 7)
    
    0 讨论(0)
  • 2021-02-19 07:55

    OCaml, the language that F# was based on, has labeled (and optional) arguments that can be specified in any order, and you can partially apply a function based on those arguments' names. I don't believe F# has this feature.

    You might try creating something like Haskell's flip function. Creating variants that jump the argument further in the argument list shouldn't be too hard.

    let flip f a b = f b a
    let flip2 f a b c = f b c a
    let flip3 f a b c d = f b c d a
    
    0 讨论(0)
  • 2021-02-19 07:57

    Just for completeness - and since you asked about other functional languages - this is how you would do it in OCaml, arguably the "mother" of F#:

    $ ocaml
    # let foo ~x ~y = x - y ;;
    val foo : x:int -> y:int -> int = <fun>
    # foo 5 3;;
    - : int = 2
    # let bar = foo ~y:3;;
    val bar : x:int -> int = <fun>
    # bar 5;;
    - : int = 2
    

    So in OCaml you can hardcode any named parameter you want, just by using its name (y in the example above).

    Microsoft chose not to implement this feature, as you found out... In my humble opinion, it's not about "poor interaction with other aspects of the language design"... it is more likely because of the additional effort this would require (in the language implementation) and the delay it would cause in bringing the language to the world - when in fact only few people would (a) be aware of the "stepdown" from OCaml, (b) use named function arguments anyway.

    I am in the minority, and do use them - but it is indeed something easily emulated in F# with a local function binding:

    let foo x y = x - y
    let bar x = foo x 3
    bar ...
    
    0 讨论(0)
  • 2021-02-19 07:58

    It's possible to do this without declaring anything, but I agree with Brian that a lambda or a custom function is probably a better solution.

    I find that I most frequently want this for partial application of division or subtraction.

    > let halve = (/) >> (|>) 2.0;;
    > let halfPi = halve System.Math.PI;;
    
    val halve : (float -> float)
    val halfPi : float = 1.570796327
    

    To generalize, we can declare a function applySecond:

    > let applySecond f arg2 = f >> (|>) arg2;;
    val applySecond : f:('a -> 'b -> 'c) -> arg2:'b -> ('a -> 'c)
    

    To follow the logic, it might help to define the function thus:

    > let applySecond f arg2 =
    -     let ff = (|>) arg2
    -     f >> ff;;
    val applySecond : f:('a -> 'b -> 'c) -> arg2:'b -> ('a -> 'c)
    

    Now f is a function from 'a to 'b -> 'c. This is composed with ff, a function from 'b -> 'c to 'c that results from the partial application of arg2 to the forward pipeline operator. This function applies the specific 'b value passed for arg2 to its argument. So when we compose f with ff, we get a function from 'a to 'c that uses the given value for the 'b argument, which is just what we wanted.

    Compare the first example above to the following:

    > let halve f = f / 2.0;;
    > let halfPi = halve System.Math.PI;;
    
    val halve : f:float -> float
    val halfPi : float = 1.570796327
    

    Also compare these:

    let filterTwoDigitInts = List.filter >> (|>) [10 .. 99]
    let oddTwoDigitInts = filterTwoDigitInts ((&&&) 1 >> (=) 1)
    let evenTwoDigitInts = filterTwoDigitInts ((&&&) 1 >> (=) 0)
    
    let filterTwoDigitInts f = List.filter f [10 .. 99]
    let oddTwoDigitInts = filterTwoDigitInts (fun i -> i &&& 1 = 1)
    let evenTwoDigitInts = filterTwoDigitInts (fun i -> i &&& 1 = 0)
    

    Alternatively, compare:

    let someFloats = [0.0 .. 10.0]
    let theFloatsDividedByFour1 = someFloats |> List.map ((/) >> (|>) 4.0)
    let theFloatsDividedByFour2 = someFloats |> List.map (fun f -> f / 4.0)
    

    The lambda versions seem to be easier to read.

    0 讨论(0)
提交回复
热议问题