Could laziness also mean that the meaning of a function depends on the context where it is called?

I'm trying to learn Haskell, and while playing around with applicative functors, I found that the puzzle bothered me.

Let's define the following function g that returns some functor:

*Main> let g = pure (2*)
*Main> :t g
g :: (Num a, Applicative f) => f (a -> a)

      

Since the return type is some kind of functor, I can use g as a parameter in both of these functions:

f1 :: (Num a) => [a -> a] -> a
f1 (g:[]) = g 3

f2 :: (Num a) => Maybe (a -> a) -> a
f2 (Just g) = g 4

      

But this means that the value returned by the g function also depends on the context in which it will be evaluated! (It can be either List or Maybe.) Is this also a property of laziness? Because until now I've been thinking about being lazy so that the value is calculated when needed, but it's already defined when it's defined (for the g in the let expression).

+3


source to share


1 answer


As @augustss said, this has nothing to do with laziness, but rather the fact that you are working with a style class. To make it clearer, you can model the type classes by explicitly passing the entry, packaging all the functions that the class defines. This method is called dictionary passing if you want to find more information about it.

Let's start with a few extensions.

{-# LANGUAGE RankNTypes      #-}
{-# LANGUAGE RecordWildCards #-}

      

And then specify the type of entry where the functions should be set Applicative

(in practice, you also have a field that states what f

is Functor

, but I omit it here for brevity).

data Applicative f =
  Applicative { pure :: forall a. a -> f a
              , app  :: forall a b. f (a -> b) -> f a -> f b
              }

      

And we can define your function g

as a record record of what f

is Applicative

and delivers the behavior you described (I saved it Num

as a class constraint, but similarly it could be translated to record).

g :: Num a => Applicative f -> f (a -> a)
g Applicative{..} = pure (2*)

      



Your two functions f1

and f2

remain valid definitions:

f1 :: Num a => [a -> a] -> a
f1 (g:[]) = g 3

f2 :: Num a => Maybe (a -> a) -> a
f2 (Just g) = g 4

      

Now we want to apply them to g

, but there is a problem: g

has a function type pending transfer Applicative f

. Well, we can define instances []

and Maybe

Applicative

:

applicativeList :: Applicative []
applicativeList =
  Applicative { pure = (:[])
              , app  = \ fs as -> fs >>= \ f -> fmap f as
              }

applicativeMaybe :: Applicative Maybe
applicativeMaybe =
  Applicative { pure = Just
              , app  = \ fs as -> fs >>= \ f -> fmap f as
              }

      

And then we need to choose the correct typecheck type for the application ( []

for f1

and Maybe

for f2

):

f1g = f1 (g applicativeList)
f2g = f2 (g applicativeMaybe)

      

+7


source







All Articles