Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there syntax for creating [Maybe a] analog, but of kind (* -> *)

I was trying to create an fmap that works on [Maybe a]. However, Maybe a has kind *, and fmap demands kind * -> *. This leads to the following unfortunate solution:

newtype Unfortunate a = Unfortunate ([Maybe a]) deriving Show

instance Functor Unfortunate
    where fmap f (Unfortunate a) = Unfortunate $ (fmap (fmap f)) a


-- |
-- >>> l = Unfortunate [Just 10, Just 1, Nothing, Just 15]
-- >>> fmap (*5) l
-- Unfortunate [Just 50,Just 5,Nothing,Just 75]

What is unfortunate is having to create a newtype at all. I'd expect it to be possible to create an instance that works for [Maybe a], for any a. I.e, something that can be called as fmap f [Just 10, Nothing].

I seem to be missing some piece of syntax. Is it possible to define such an instance?

like image 505
josinalvo Avatar asked Jun 25 '21 01:06

josinalvo


Video Answer


3 Answers

MaybeT does exactly (and more) what your Unfortunate does:

λ> import Control.Monad.Trans.Maybe
λ> (*2) <$> MaybeT [Just 1, Just 2, Nothing]
MaybeT [Just 2,Just 4,Nothing]
λ> pure 1 :: MaybeT [] Int
MaybeT [Just 1]

You can also use the DeriveFunctor flag and have the "unfortunate functor" derived mechanically:

λ> :set -XDeriveFunctor 
λ>  newtype Unfortunate a = Unfortunate ([Maybe a]) deriving (Functor, Show)
λ> (*2) <$> Unfortunate [Just 1, Just 2, Nothing]
Unfortunate [Just 2,Just 4,Nothing]
λ> 

There is also generic-functor if you want to go the generics route.

Edit:

Another option is using GeneralizedNewtypeDeriving:

λ> :set -XGeneralizedNewtypeDeriving 
λ> (*2) <$> Unfortunate (MaybeT [Just 1, Just 2, Nothing])
Unfortunate (MaybeT [Just 2,Just 4,Nothing])

Edit2:

As Daniel Wagner points out in the comments, we can also use:

λ> import Data.Functor.Compose
λ> (*2) <$> Compose [Just 1, Just 2, Nothing]
Compose [Just 2,Just 4,Nothing]
like image 123
pedrofurla Avatar answered Oct 14 '22 07:10

pedrofurla


What is unfortunate is the creation of the newtype. I'd expect it to be possible to create an instance that works for [Maybe a], for any a.

There is already such an instance. It works, not only for any [Maybe a], but for any [a]. And since a can be unified with Maybe a, you can use it for [Maybe a] as well.

Now, clearly this is not actually what you meant. When specialized to this type, fmap has type

(Maybe a -> Maybe b) -> [Maybe a] -> [Maybe b]

but you wish it took a function of type (a -> b) instead. I bring this up to explain why the use of a separate type cannot be avoided1. There is already an instance for the types you want, and so you cannot just define a new instance that replaces that behavior. You need a different type to attach that instance to.

As discussed in another answer, there are generic types already defined that can be composed to get the setup you want. But you can't just use a bare [Maybe a].


1 There are some language extensions you could turn on to break the type system to allow this, but this can have surprising consequences, and I won't go into that here.

like image 7
amalloy Avatar answered Oct 14 '22 07:10

amalloy


Ths is a perfect use case for DerivingVia (it was brought to my attention that I didn't read the question carefully enough, you have to use a newtype one way or the other: this solution is not what you were hoping for but it is idiomatic)

{-# Language DerivingVia              #-}
{-# Language StandaloneKindSignatures #-}

import Control.Applicative (Alternative)
import Control.Monad (MonadPlus)
import Control.Monad.Fix (MonadFix)
import Control.Monad.Trans.Maybe
import Control.Monad.Zip (MonadZip)
import Data.Kind (Type)

type    Fortunate :: Type -> Type
newtype Fortunate a = Fortunate [Maybe a]
 deriving
  ( Functor, Foldable, Applicative, Alternative
  , Monad, MonadPlus, MonadFail, MonadFix, MonadZip
  )
 via MaybeT []

Some of these instances like (Functor, Foldable, Applicative, Alternative) can be derived via Compose Maybe [].

To list what instances can be derived, use the :instances command

>> :instances MaybeT []
instance [safe] Alternative (MaybeT [])
  -- Defined in ‘Control.Monad.Trans.Maybe’
instance [safe] Applicative (MaybeT [])
  -- Defined in ‘Control.Monad.Trans.Maybe’
...
>> :instances Compose Maybe []
instance Alternative (Compose Maybe [])
  -- Defined in ‘Data.Functor.Compose’
instance Applicative (Compose Maybe [])
  -- Defined in ‘Data.Functor.Compose’
...

Because Fortunate is an applicative you can derive (Semigroup, Monoid, Num, Bounded) through pointwise (idiomatic, applicative) lifting:

import Data.Monoid (Ap(..))

..
 deriving (Semigroup, Monoid, Num, Bounded)
 via Ap Fortunate a

where (<>) = liftA2 (<>), abs = liftA abs, mempty = pure mempty, minBound = pure minBound.

like image 4
Iceland_jack Avatar answered Oct 14 '22 07:10

Iceland_jack