Mathematically, the binary operation for function application is an endo-Functor/Monad.
pipe :: (a -> b) -> a -> b
pipe = \f -> \a -> f a
(|>) :: a -> (a -> b) -> b
(|>) = flip pipe
infixl 1 |>
It's called pipeline-operator, and in Haskell, predefined as
https://hackage.haskell.org/package/base-4.16.0.0/docs/Data-Function.html#v:-38-
(&) :: a -> (a -> b) -> b
monadTestPipe :: IO ()
monadTestPipe = do
"Monad Laws========" & print
let i = id
let a = (3 :: Int)
let f = (\a -> a 1) >>> i
let g = (\a -> a * 2) >>> i
let m = f a -- 4
"---------------------------------" & print
do
"-- Left/Center/Right identity" & print
let lft = a & i & f
let ctr = a & f -- = m
let rit = a & f & i
---------------------------------
lft & print -- 4
ctr & print -- 4
rit & print -- 4
"---------------------------------" & print
do
"-- Associativity" & print
-- m = f a -- 4
(m & f) & g
& print -- 10 = (4 1)x2
m & (\x -> f x & g)
& print -- 10
"---------------------------------" & print
Why is the pipeline-operation (&) :: a -> (a -> b) -> b
not treated (at least not integrated) as Monad in Haskell?
Or, it's probably fine to say, I've never read that in any Haskell wiki or books.
Edit: I want to make my question as concise as possible in order to prevent diffusion of issues, but
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
>>=
is simply a symbol of Monad type class in Haskell, and &
can be integrated to >>=
because &
is a monad operator in mathematical sense.
return
is id :: a -> a
CodePudding user response:
It is treated as the bind operation (aka (>>=)
) of a monad (specifically the identity monad), just not directly. You must use a wrapper, due to the way the type system works: you cannot have a type synonym like type Id a = a
as an instance of Monad
and there are no type-level lambdas.
type Id a = a
simply describes a synonym: a
and Id a
are the same type.
You cannot have an instance like
instance Monad Id
using the above definition of Id
because Id
is in essence the same as a type-level lambda \a -> a
(if such a thing existed in Haskell), and Haskell doesn't have this kind of type-level lambda, since they would make Haskell's type system undecidable.
However, this is exactly one of the main reasons the newtype
construct was created. It creates a new type (as its name suggests) which wraps the old type and only exists to distinguish between the old type and the new one at a type-level. They even have the same runtime representation. You can read more about the difference between the two here.
In this case, the wrapper is called Identity
.