最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Compose function signature - Stack Overflow

programmeradmin8浏览0评论

I've read that the position of g :: A -> B and f :: B -> C, pronounced (“f posed of g”), results in another function (arrow) from A -> C. This can be expressed more formally as

f • g = f(g) = pose :: (B -> C) -> (A -> B) -> (A -> C)

Can the above position be also defined as below? Please clarify. In this case the pose function takes the same two functions f and g and return a new function from A -> C.

f • g = f(g) = pose :: ((B -> C), (A -> B)) -> (A -> C)

I've read that the position of g :: A -> B and f :: B -> C, pronounced (“f posed of g”), results in another function (arrow) from A -> C. This can be expressed more formally as

f • g = f(g) = pose :: (B -> C) -> (A -> B) -> (A -> C)

Can the above position be also defined as below? Please clarify. In this case the pose function takes the same two functions f and g and return a new function from A -> C.

f • g = f(g) = pose :: ((B -> C), (A -> B)) -> (A -> C)

Share Improve this question edited Jul 27, 2016 at 18:16 user6445533 asked Jul 27, 2016 at 14:39 Jyoti Prasad PalJyoti Prasad Pal 1,6293 gold badges29 silver badges42 bronze badges 3
  • What book are you reading? – ErikR Commented Jul 27, 2016 at 14:41
  • @Erik, functional programming in javascript by Luis Atencio – Jyoti Prasad Pal Commented Jul 27, 2016 at 14:43
  • 1 Tupling of arguments, as you have done in your last version, is monly referred to as the "uncurried version" of a function. Wikipedia has an acceptable article (en.wikipedia/wiki/Currying). Fun fact: 'curry' is taken from the name of the same Mathematician as 'Haskell', Haskell Curry (en.wikipedia/wiki/Haskell_Curry). – Thomas M. DuBuisson Commented Jul 27, 2016 at 16:30
Add a ment  | 

3 Answers 3

Reset to default 8

First we need to get some things right:

  • f ○ g means something quite different from f(g).

    • The former is a function that, given an argument x, will first feed it to g, then pass on the result to f, and output that final result, i.e. f(g(x)).
    • OTOH, f(g) means you apply the function f to the value g right away, without waiting for any argument. (g just happens to have a function type, but in functional languages, functions can be passed around just like any other values / arguments).

    Unless you're dealing with some pretty wacky polymorphic functions, one of these will be ill-typed. For example, a well-typed position might be

    sqrt ○ abs :: Double -> Double
    

    whereas a well-typed application could be (at least in Haskell)

    map(sqrt) :: [Double] -> [Double]
    

    I'll assume in the following you're talking about f ○ g.

  • Type signatures must be given for a function itself, not for a function applied to some arguments. This is something that loads of people get utterly wrong: in f(x), you have a function f and an argument x. But f(x) is not a function, only the value that's the result of applying a function to a value! So, you shouldn't write something like f ○ g :: ... (unless you're actually talking only about the type that results from the position). Better write just ○ :: ... (or, in Haskell, (○) :: ...).

  • Function arrows aren't associative. Most mathematicians likely won't even know what X -> Y -> Z is supposed to mean. What it means in languages like Haskell may actually be somewhat surprising:

    X -> Y -> Z  ≡  X -> (Y -> Z)
    

i.e. this is the type of a function that first takes only an argument of type X. The result will be again a function, but one that takes only an argument of type Y. This function will have, if you like, the X value already built-in (in a so-called closure, unless the piler optimises that away). Giving it also the Y value will allow the function to actually do its job and finally yield the Z result.

At this point you already have your answer, pretty much: indeed the signatures X -> Y -> Z and (X, Y) -> Z are essentially equivalent. The process of rewriting this is called currying.

To answer your question in particular: most languages don't normally do any currying, so the signature ((B -> C), (A -> B)) -> (A -> C) is actually more correct. It corresponds to a function you can call as

   pose(f,g)

OTOH, the curried signature (B -> C) -> (A -> B) -> (A -> C) means that you need to feed in the arguments one by one:

   pose(f)(g)

Only in languages like Haskell is this the standard style, but you don't need the parens there: all the following are parsed the same in Haskell

   pose(f)(g)
   pose f g
   (pose) f g
   (.) f g
   f . g

where . is in fact the position operator, which as you can see from the documentation has type

(.) :: (b -> c) -> (a -> b) -> a -> c

Since you marked your question with Javascript here is an answer from a Javascript point of view.

Assuming I understand your signature properly, you want to adapt the position function as follows: (f, g) => x => f(g(x));. Sure, that works, but you lose flexibility and gain uhm, nothing.

The original curry function is defined in curried form that means, it expects always a single argument. If every function in your whole code expects exactly one argument, then there is no more arity (well, in most cases). It is abstracted away. Currying facilitates function position, because functions always return a single value. Curried functions are like building blocks. You can put them together in almost any way:

    const p = f => g => x => f(g(x)),
     p2 = p(p)(p),
     add = y => x => x + y,
     inc = x => x + 1,
     sqr = x => x * x;
    
    console.log(p(sqr)(inc)(2)); // 9
    console.log(p(add)(sqr)(2)(3)); // 7
    console.log(p2(sqr)(add)(2)(3)); // 25

As you can see only in the latter case we must consider the arity.

Currying can only develop its benefits if it is consistently applied for each function of your codebase, because it has a systemic effect.

First, an open circle is more monly used: f ∘ g.

Second, it would more properly be pronounced "f posed with g". ("f posed of g" sounds like f is made up of g, rather than a new function made up of both.)

Finally, the two types are essentially the same, differing only in how you expect to pass functions to the pose function. The first defines the type of a fully curried function, such that pose takes one function as an argument, and returns a new function that takes the second function as an argument and returns the posed. This means with f :: B -> C and g :: A -> B, you can define either (using Haskell syntax)

pose :: (B -> C) -> (A -> B) -> (A -> C)
pose f g = \x -> f (g x)

or the uncurried version

pose' :: ((B -> C), (A -> B)) -> (A -> C)
pose' (f, g) = \x -> f (g x)

Either way, the return value is the same; the only difference is in how the arguments are passed. You could write h = pose f g or you could write h = pose' (f, g).

发布评论

评论列表(0)

  1. 暂无评论