17

I am learning Haskell. I'm sorry for asking a very basic question but I cant seem to find the answer. I have a function f defined by :

f x = g x x

where g is an already defined function of 2 arguments. How do I write this pointfree style? Edit : without using a lambda expression.

Thanks

user1188374
  • 829
  • 2
  • 7
  • 9

3 Answers3

21

f can be written with Control.Monad.join:

f = join g

join on the function monad is one of the primitives used when constructing point-free expressions, as it cannot be defined in a point-free style itself (its SKI calculus equivalent, SIIap id id in Haskell — doesn't type).

Will Ness
  • 70,110
  • 9
  • 98
  • 181
ehird
  • 40,602
  • 3
  • 180
  • 182
  • 2
    I think you mean `f = join g` – dflemstr Feb 06 '12 at 23:52
  • Can you explain why my answer would not be `point free`? I guess I have a misunderstanding. – pmr Feb 06 '12 at 23:54
  • @pmr: Point-free means the elimination of named parameters; `x` is a point in your definition. – ehird Feb 06 '12 at 23:55
  • @pmr, writing in point-free style means that you omit the introduction of explicit variables. While you have made `f` "explicit-argument-less," you still introduce the explicit variable `x` later on in the definition, making the definition pointful. – dflemstr Feb 06 '12 at 23:57
  • This SKI thing is very interesting. Indeed SII is what I need. I don't understand what you mean by "ap id id" and "doesn't type". Also I don't know what join is but I'll look into it, thanks. – user1188374 Feb 06 '12 at 23:57
  • 1
    @user1188374, the `join` function is a fundamental function for monads, the monad in this case being the function monad, `(->) r` for some `r`. The definition of `join :: Monad m => m (m a) -> m a`; substituting `(->) r` for `m` yields `join :: (r -> r -> a) -> (r -> a)`. – dflemstr Feb 07 '12 at 00:01
  • 1
    @user1188374: `join` is a primitive of point-free style because the translation of `SII` to Haskell, `ap id id`, isn't valid — it has a type error (specifically, it fails the [occurs check](http://en.wikipedia.org/wiki/Occurs_check)). It works in the SKI combinator calculus because it's untyped. – ehird Feb 07 '12 at 00:04
  • @dflemstr Thanks. I was under the wrong impression that it only meant not to have any variables on the left hand side. – pmr Feb 07 '12 at 00:07
  • 2
    `join` is equal to `flip ap id`, so it could be argued that it is not a primitive. Be aware that while some SKI expressions don't type, they are not usually unique, and sometimes an alternative will do better. For example, `I = SKK = SKS`, but of those two only `ap const const` has the type that you want. – Ben Millwood Feb 16 '12 at 11:33
  • 1
    @dflemstr: Where is the function monad instance actually defined in Haskell / libraries? In `ghci`, `Control.Monad.join (+)` gives an error: ` No instance for (Monad ((->) a0)) arising from a use of `join' Possible fix: add an instance declaration for (Monad ((->) a0))`. (I see that Control.Monad haddock has `instance Monad ((->) r)`, but there is no corresponding source (because it's primitive in GHC?) and there's a step missing for me.) – misterbee Feb 16 '12 at 23:38
  • 2
    @misterbee it is declared in [Control.Monad.Instances](http://hackage.haskell.org/packages/archive/base/latest/doc/html/src/Control-Monad-Instances.html). – dflemstr Feb 17 '12 at 00:40
  • @dflemstr: Ah, thanks. I guess that was obvious in retrospect, but it's hard to search for `->`, and of course [`-- This module deliberately declares orphan instances:`](http://hackage.haskell.org/packages/archive/base/latest/doc/html/src/Control-Monad-Instances.html), which also hinders discoverability.... – misterbee Feb 17 '12 at 00:55
  • 1
    @ehird: `join` cannot be considered as a primitive, because it can be implemented in terms of `<*>` and `pure` (`S` and `K`, respectively). `SII` is equivalent to `λx.xx` while `join` is `λfx.fxx`. The former cannot be typed no matter what expression you use. You could define `join = S(S(KS)(S(S(KS)K)(KI)))(KI)`. – Vitus May 20 '12 at 15:18
  • 1
    Or, well, also `join = SS(KI)`. – Vitus May 21 '12 at 18:22
9

This is known as "W" combinator:

import Control.Monad
import Control.Monad.Instances
import Control.Applicative

f = join g       -- = Wg        (also, join = (id =<<))
  = (g `ap` id)  -- \x -> g x (id x) = SgI
  = (<*> id) g   --                  = CSIg
  = g =<< id     -- \x -> g (id x) x
  = id =<< g     -- \x -> id (g x) x

S,K,I are one basic set of combinators; B,C,K,W are another - you've got to stop somewhere (re: your "no lambda expression" comment):

_B = (.)     -- _B f g x = f (g x)     = S(KS)K
_C = flip    -- _C f x y = f y x       = S(S(K(S(KS)K))S)(KK)
_K = const   -- _K x y   = x
_W = join    -- _W f x   = f x x       = CSI = SS(KI) = SS(SK)
_S = ap      -- _S f g x = f x (g x)   = B(B(BW)C)(BB) = B(BW)(BBC)
   = (<*>)                                -- from Control.Applicative
_I = id      -- _I x     = x           = WK = SKK = SKS = SK(...)

{-
Wgx = gxx 
    = SgIx = CSIgx 
           = Sg(KIg)x = SS(KI)gx
    = gx(Kx(gx)) = gx(SKgx) = Sg(SKg)x = SS(SK)gx

-- _W (,) 5 = (5,5)
-- _S _I _I x = x x = _omega x         -- self-application, untypeable
-}
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 2
    The stopping point is one combinator. One example is the `ι` (iota) combinator - `λf.fSK`, `SKI` is then expressed as `S = ι(ι(ι(ιι)))`, `K = ι(ι(ιι))` and `I = ιι`. This base is not so friendly for simply typed λ-calculus, `ιι` doesn't even typecheck. `U = λf.fKSK` is a bit better (I don't know if it has a name, so I'm just calling it `U` as universal); `S = U(UU)` and `K = UUU` – Vitus Jun 15 '12 at 14:11
  • @Vitus [yes, yes](http://en.wikipedia.org/wiki/Combinatory_logic#One-point_basis). It's more a question of convenience I guess, *where* to stop, in a *practical* setting. In Haskell for instance, it's convenient that we have SKI *and* BCKW combinators. – Will Ness Jun 15 '12 at 14:33
  • 1
    ... also, `f = g =<< id = id =<< g`. – Will Ness Jan 02 '16 at 02:31
1

I got here by pure chance, and I want to offer my solution, as nobody has mentioned lifting yet in this thread, not explicitly at least.

This is a solution:

f = liftM2 g id id

How to look at it?

  • g has type a -> a -> b, i.e. it takes two values of some type (the same type for both, otherwise the definition the OP gave of f wouldn't make sense), and gives back another value of some type (not necessarily of the same type as the arguments);

  • lift2M g is the lifted version of g and it has type (Monad m) => m a -> m a -> m b: it takes two monadic values, which are each a value in a so-far-unspecified context, and gives back a monadic value;

  • when passing two functions to liftM2 g, the die is cast on what the context of the Monad is: it is that the values are not in there yet, but will eventually be, when the function will receive the arguments it needs; in other words, functions are monads that store their own future values; therefore, lift2M g takes in input two functions (or, the future values of two functions), and gives back the another function (or, its future value); knowing this, its type is the same as above if you change m to (->) r, or r ->: (r -> a) -> (r -> a) -> (r -> b)

  • the two functions that we pass are both id, which promises it'll give back the same value it receives;

  • liftM2 g id id is therefore a function of type r -> b that passes its argument to those two ids, which let it unchanged and forward it to g.

In a similar fashion, one can exploit that functions are applicative functors, and use this solution:

f = g <$> id <*> id
Enlico
  • 23,259
  • 6
  • 48
  • 102
  • or `liftA2`, the same as `liftM2` for functions (i.e. Reader monad): `liftA2 k f g x = k (f x) (g x)`. (this just in: `liftA2 (,) === (&&&)` :) ). – Will Ness Jun 06 '20 at 17:05
  • Hi @WillNess, every comment of yours adds one empty checkbox on my to-learn list, ahahah. This language just makes me wanna come back to uni. Well, I wouldn't even know where I can study it in Italy :/. I guess I'd have to search. – Enlico Jun 06 '20 at 17:08
  • 1
    search for Eugenio Moggi, for starters. :) (BTW, `g <$> x <*> y` === `liftA2 g x y`, more or less by definition IIRC) – Will Ness Jun 06 '20 at 17:16