Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applicative operators <* and *>, type signature implication

I recently saw a simple example that brought <* and *> to light.

validate :: String -> Maybe String
validate s =  if s=="" then Nothing else Just s

>validate "a" *> validate "b"
Just "b"
>validate "" *> validate "b"
Nothing
>validate "a" <* validate "b"
Just "a"
>validate "a" <* validate ""
Nothing
>validate "a" <* validate "b" <* validate "c"      
Just "a"
>validate "a" *> validate "b" <* validate "c"      
Just "b"

This shows that the effects are important even if the values they produce are not.

My question is about the type signatures.

(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
  • Do these type signatures actually imply the behavior shown above?

I can see how one could reason "Obviously we have an Applicative - so that says something about behavior in general. For operator *>, since we are throwing away the left value, the only possible meaning this function could have would be how the effect of the left hand side affects the entire operation."

In the case of Maybe - then it seems that yes - the behavior is implied. For Either, likewise the implication holds and the error would be propagated on an effect 'failure'.

Note I can only say the above because I now know how the implementation works, where my question pertains to the seasoned functional programmer who sees a signature like this the first time.

I have read where a type signature like [a] -> b :: Int (probably not real code there) all but implies the implementation as the length of the list.

I have also searched for "implying implementation from type signature" and found that people have-been/are working on such things - but in general cannot be done (Uh - without that new GitHub thing :--)

So perhaps I have answered my own question but would appreciate any other answers or comments. I am still new to Haskell and after a number of false starts over the years, it is finally starting to sink in. And I have only scratched the surface...

Thanks

like image 282
user49011 Avatar asked Dec 10 '21 13:12

user49011


People also ask

What is the implication operator?

Basic Logic - The Implication Operator In binary logic, we know about operators such as AND, OR, NOT. Another operator that is important in logic and in test design is the impliesoperator. A ->B (A impliesB)

What is the implies operator in logic?

In binary logic, we know about operators such as AND, OR, NOT. Another operator that is important in logic and in test design is the impliesoperator. A ->B (A impliesB) Another way of stating the implies operator is with if…then: IF A THEN B The truth table is: A

Which is the correct way to use the implies operator?

Another way of stating the implies operator is with if…then: IF A THEN B The truth table is: A B A ->B 0 0 1 0 1 1 1 0 0 Logical contradiction!

How do you use implication in a sequence?

The implication construct can be used only with property definitions. It cannot be used in sequences. There are 2 types of implication: The overlapped implication is denoted by the symbol |->. If there is a match on the antecedent, then the consequent expression is evaluated in the same clock cycle.


1 Answers

(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a

Do these type signatures actually imply the behavior shown above?

Of course not. The two operations could be implemented simply as

apR :: (Applicative f) => f a -> f b -> f b
apR a b = b

apL :: (Applicative f) => f a -> f b -> f a
apL a b = a

just ignoring the Applicative constraint on f.

And in the same way [a] -> Int does not imply the implementation being length. It could just as well be

foo :: [a] -> Int
foo _ = 42

But in both cases these are not the "right" implementations in the sense that the inferred types are different from the signatures given.

Your question is then, perhaps, a bit different, like, suppose there is an implementation with the matching inferred type. Does it follow that it does the thing we intended?

The answer seems to still be no. For one, we can define

apR2 :: (Applicative f) => f a -> f b -> f b
apR2 a b = pure (\a _ b -> b) <*> a <*> b <*> b

For some types, like your examples, it won't make a difference. But in general, doing the effects twice is different than doing them only once.

Another, even more meaningfully "wrong" implementation (with thanks to Daniel Wagner for the comments), is

apR2b :: (Applicative f) => f a -> f b -> f b
apR2b a b = pure (\b a -> b) <*> b <*> a

Now even your examples won't always work, because the order of effects is different -- it "does" b's first, before the a's.

like image 190
Will Ness Avatar answered Oct 20 '22 21:10

Will Ness