3

I've read in a book that the function signature of tap function (also called K-Combinator) is below:

tap :: (a -> *) -> a -> a

"This function takes an input object a and a function that performs some action on a. It runs the given function with the supplied object and then returns the object."

  1. Can someone help me to explain what is the meaning of star (*) in the function signature?
  2. Are below implementation correct?
  3. If all the three implementation are correct, which one should be used when? Any examples?

Implementation 1:

const tap = fn => a => { fn(a); return a; };

tap((it) => console.log(it))(10); //10

Implementation 2:

const tap = a => fn => { fn(a); return a; }; 

tap(10)((it) => console.log(it)); //10

Implementation 3:

const tap = (a, fn) => {fn(a); return a; };

tap(10, (it) => console.log(it)); //10
Paul Sweatte
  • 24,148
  • 7
  • 127
  • 265
Jyoti Prasad Pal
  • 1,569
  • 3
  • 26
  • 41
  • 2
    which *K-combinator* is this ... it's not [this one](https://en.wikipedia.org/wiki/Combinatory_logic#Combinatory_terms) – Random Dev Jul 27 '16 at 13:46
  • oh wait I get it - yes your implementation 2 looks something like `K + sideeffects` ... don't know if I like this (the side-effects) - would not call it K-combinator – Random Dev Jul 27 '16 at 13:50
  • btw: the `*` probably stands for *something (type?) I don't care* - in Haskell it's usually used for kinds (think: type of types) which would not work here at all – Random Dev Jul 27 '16 at 13:51
  • 1
    is this what? - I would say `K = const` in Haskell so the types would not match - I have no clue what `tap` is supposed to be – Random Dev Jul 27 '16 at 13:56
  • @WillNess AFAIK ML uses `:` not `::` in signatures – Random Dev Jul 27 '16 at 13:57
  • 2
    It seems like the `*` means `Unit` or `void` from `C` - as in a function `a -> ()` in Haskell. Calling this function a `K combinator` is a little misleading - it can't even exist in a pure language (or in combinatory logic, lambda calculus, etc.). There is precisely one function of type `forall a . (a -> ()) -> a -> a` in Haskell, and it is `const id`. As for your implementations, 2 is `flip tap` and 3 is `uncurry tap` (1 is `tap` proper), so they are all isomorphic. In other words, they are all correct, and it doesn't matter which one you use. – user2407038 Jul 27 '16 at 13:57
  • I think the *haskell* tag can be safely removed - only the type-signatures looks a bit like Haskell but then it's all about JavaScript ... so who cares about types right? – Random Dev Jul 27 '16 at 14:00
  • @WillNess I think "I get" where Jyoti is aiming for - it should kindof materialize the side-effects of `a` applied to `fn` - but as I said: I would not call this K-combinator and I would not do this in Haskell - but in JavaScript it might be a sensible thing to do if you try to chain things in *functional style* – Random Dev Jul 27 '16 at 14:02
  • @Carsten: I've read that K-combinator is used to do something with a value for side-effects, but keep the value around which can be useful while debugging by writing to log, etc. Is this not correct? If not, may I know then what is the real use of the K-combinator where there are no side effects. – Jyoti Prasad Pal Jul 27 '16 at 14:02
  • if you want to read about those combinators follow the link to Wikipedia I gave you - there are no side-effects in this theoretic approach to logic/math at all ;) ... and pure functional programming pretents so too ;9 – Random Dev Jul 27 '16 at 14:04
  • @Carsten *"try to chain things in a functional style"* that's precisely what's going on here, its a way to introduce side-effects in to a series of data structure transforms (maps and folds). – Jared Smith Jul 27 '16 at 15:12
  • @Carsten I guess `tap` isn't known in Haskell, right? I don't like it in Javascript either, because it is kind of pseudo functional. Sure, it returns a value like every function should, but the triggered side effect isn't wrapped in a context. Consequently a function composition with `tap` is coupled to its context. –  Jul 27 '16 at 16:03

1 Answers1

6

This looks much like the Ramda definition. The * in there is probably a mistake. (Disclaimer: I'm one of the Ramda authors.) It should probably read

// tap :: (a -> b) -> a -> a

An implementation like your first one:

const tap = fn => a => { fn(a); return a; };

or Ramda's version:

const tap = curry((fn, a) => { fn(a); return a; });

match that signature and are useful IMHO mostly in debugging contexts. I use it to temporarily introduce logging statements into functional pipelines1:

// :: Map String User
const users = fetchUsersFromSomewhere();

// :: [Comment] -> [Number]  
const userRatingForComments = R.pipe(
    R.pluck('username'),     // [Comment] -> [String]
    R.tap(console.log),      // for debugging, need to include `bind` in browser envs
//  ^^^^^^^^^^^^^^^^^^      
    R.map(R.propOf(users)),  // [String] -> [User]
    R.pluck('rating')        // [User] -> [Number]
);

This is really not the K combinator, though.


1 This code sample is from an old article of mine on Ramda.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • 1
    Supplement: `K` is defined as `const K = x => y => x`. Then you can use it as a functional constant: `let konst = K(true); konst(2 > 3);` yields `true`. This is very useful when you pass a predicate to a function composition and the computation needs to yield `true`, no matter what the actual result is. –  Jul 27 '16 at 15:55
  • @LUH3417 any practical example? – Jyoti Prasad Pal Jul 28 '16 at 11:32
  • @Jyoti put a new question. –  Jul 28 '16 at 11:34
  • 1
    @Scott Sauyet why do you think * in the signature is wrong. In one of the comments by Carsten, it is said that it means void/anything. For example, the function could be writing to log, etc and does not return any value. I could also see * in the tap function signature in the link [Ramda definition](http://ramdajs.com/0.21.0/docs/#tap). – Jyoti Prasad Pal Jul 28 '16 at 11:41
  • Ramda has mostly dropped its [discussion](https://github.com/ramda/ramda/issues/1554) about cleaning up the type signatures, but at some point perhaps we'll return to it. In this case, `a -> b` is much clearer. The function returns an arbitrary type, one that has no relation to the other types in question, but not likely a function that can return *anything*. That's the distinction. `a -> b` represents a function which accepts an element of some arbitrary type and returns an element of some other (not necessarily distinct) type. `a -> *` is for one that can return *anything* at all. – Scott Sauyet Jul 28 '16 at 12:07
  • @Scott Sauyet Thanks for clarifying it. – Jyoti Prasad Pal Jul 28 '16 at 13:47