I have a datatype which keeps track of a 'block' of numbers (kinda like a matrix).
newtype Block a = Block [[a]]
I would like to make it an instance of Functor. However, I am trying to do it in such a way that fmap can apply to the whole list block, so that fmap has type fmap :: [[a]] -> [[b]] instead of type fmap :: a -> b.
The reason is because I would like to map into my Block functor functions like transpose which apply to the list block [[a]] not to each element a. That is, I would like to be able to define functions like
transposeBlock :: Block a -> Block a
transposeBlock = (transpose <$>)
I tried to declare my functor instance as the following.
instance Functor (Block [[a]]) where
fmap f (Block x) = Block (f x)
But I ran into the type errors that arise from attempting to compile this.
error:
• Expecting one fewer argument to ‘Block [[a]]’
Expected kind ‘* -> *’, but ‘Block [[a]]’ has kind ‘*’
• In the first argument of ‘Functor’, namely ‘Block [[a]]’
In the instance declaration for ‘Functor (Block [[a]])’
What are some ways that I could map functions into the list block [[a]] of my Block type?
3 Answers 3
Sorry, you can't call that function fmap. But that's okay, there's lots of other good names out there.
onBlock :: ([[a]] -> [[b]]) -> Block a -> Block b
onBlock f (Block v) = Block (f v)
You can even give it a name full of infixy punctuation goodness if you want.
(<#>) :: ([[a]] -> [[b]]) -> Block a -> Block b
f <#> Block v = Block (f v)
4 Comments
fmap? I jumped on this question quickly, thinking it was easy to fix. Then it didn't sit right and I tried it quickly and couldn't get it to work. Is it because of the kind of Functor hiding the a in your instance declaration?Block an instance of Functor; e.g. instance Functor Block where fmap f (Block x) = Block (fmap (fmap f) x). But the type of fmap doesn't unify with the type of onBlock, so using onBlock as the implementation is a non-starter.a in fmap :: (a -> b) -> f a -> f b will never unify with [[a]]. Is my understanding correct?fmap is short for forall a b. (a -> b) -> f a -> f b, in other words, it has to work for any types a and b -- including ones which are not lists. An "fmap" that does not do this doesn't count as a functor -- for example, the generic function foo x = fmap (+1) x should work if x is an f Int, but it wouldn't work on Block Int if you got your way.Others have already commented on the opportunity of using fmap for this.
Wrapping / unwrapping newtypes is boring, but safe coercions can make this immediate.
import Data.Coerce
newtype Block a = Block [[a]]
onBlock :: ([[a]] -> [[b]]) -> Block a -> Block b
onBlock = coerce
Indeed, one might even avoid onBlock completely, and directly use coerce as needed.
2 Comments
onBlock is still worth defining -- every time I have tried to use coerce bare, I end up drowning in type annotations.coerce is powerful, but sometimes it needs to be restricted.The easiest way to achieve that would be
newtype BlockWr a = Block a deriving (Functor)
type Block a = BlockWr [[a]]
Now transposeBlock = (transpose <$>) would work.
But I wouldn't recommend this. BlockWr is just the identity functor, this is not very useful.
Really, the point is that Block a is isomorphic to [[a]]. You can witness that:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Lens.TH
newtype Block a = Block {_blockMat::[[a]]}
makeLenses ''Block
transposeBlock :: Block a -> Block a
transposeBlock = blockMat %~ transpose
fmap . fmap :: (a -> b) -> [[a]] -> [[b]]?