sevo
sevo

Reputation: 4609

Learning Haskell - How to simplify expressions?

Is there any way to take the pain out of expression simplification?

For example, given this expression:

(+) <$> a <*> b $ 1

I would love to see a tool that would explain what it means. It's quite laborious for beginners (finding correct instance function definition in sources, checking operator precedence) to simplify expressions with all steps involved:

fmap (+) a <*> b $ 1

See definition in Data.Functor

(.) (+) a <*> b $ 1  

See fmap in Control.Monad.Instances for instance Functor ((->) r)

and so on.

EDIT: To clarify, I'm looking for a way to rewrite expression using actual function definitions so that newcomer could understand the outcome of this expression. How to tell that (<$>) = fmap here? I don't know how to find a particular instance definition (source) using hoogle and other tools.

EDIT: Changed incorrect original expression to match following reductions.

Upvotes: 11

Views: 1664

Answers (3)

bheklilr
bheklilr

Reputation: 54068

I find that the easy way is to use typed holes available in GHCi 7.8:

> (*10) <$> _a $ 1
Found hole ‘_a’ with type: s0 -> b
Where: ‘s0’ is an ambiguous type variable
       ‘b’ is a rigid type variable bound by
           the inferred type of it :: b at <interactive>:4:1
Relevant bindings include it :: b (bound at <interactive>:4:1)
In the second argument of ‘(<$>)’, namely ‘_a’
In the expression: (* 10) <$> _a
In the expression: (* 10) <$> _a $ 1

So this tells me that a :: s0 -> b. Next is to figure out the order of operators:

> :i (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
infixl 4 <$>
> :i ($)
($) :: (a -> b) -> a -> b
infixr 0 $

So this says that $ is highly right-associative, and given it's type we see that it's first argument must be a function, so a must be a function (double confirmation). This means that (*10) <$> a $ 1 is the same as ((*10) <$> a) $ 1, so we'll focus on (*10) <$> a first.

> :t ((*10) <$>)
((*10) <$>) :: (Num a, Functor f) => f a -> f a
> :t (<$> _a)
Found hole ‘_a’ with type: f a
Where: ‘a’ is a rigid type variable bound by
           the inferred type of it :: (a -> b) -> f b at Top level
       ‘f’ is a rigid type variable bound by
           the inferred type of it :: (a -> b) -> f b at Top level
In the second argument of ‘(<$>)’, namely ‘_a’
In the expression: (<$> _a)

So we need a to be a functor. What are available instances?

> :i Functor
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b
  (<$) :: a -> f b -> f a
        -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘Data.Maybe’
instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor ZipList -- Defined in ‘Control.Applicative’
instance Monad m => Functor (WrappedMonad m)
  -- Defined in ‘Control.Applicative’
instance Control.Arrow.Arrow a => Functor (WrappedArrow a b)
  -- Defined in ‘Control.Applicative’
instance Functor (Const m) -- Defined in ‘Control.Applicative’
instance Functor [] -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((,) a) -- Defined in ‘GHC.Base’

So (->) r happens to be one, which is awesome because we know a has to be a function. From the Num constraint, we can determine that r must be the same as Num a => a. This means that (*10) <$> a :: Num a => a -> a. From that we then apply 1 to it, and we'd get (*10) <$> a $ 1 :: Num a, where a is some unknown function.

All this is discoverable using GHCi using :t and :i, along with typed holes. Sure, there's a fair number of steps involved, but it never fails when you're trying to break down a complex expression, just look at the types of different sub-expressions.

Upvotes: 6

MasterMastic
MasterMastic

Reputation: 21286

GHCi was wonderfully and correctly suggested, and I suggest it too.

I also want to suggest Hoogle, because with the instant search enabled (at the top sidebar at the right there's a button for it), you can search for functions very rapidly, it can provide much, much more information than GHCi, and the best part is that you don't have to mention modules to search in them1. This is in contrast to GHCi where you have to import first:

ghci> :t pure
<interactive>:1:1: Not in scope: ‘pure’
ghci> :m +Control.Applicative
ghci> :t pure
pure :: Applicative f => a -> f a

The Hoogle link above is just one (from the Haskell.org site). Hoogle is a program which you can also install on your machine (cabal install hoogle) and execute queries from the command-line (hoogle your-query).
Sidenote: you'd have to run hoogle data to gather the information first. It requires wget/curl, so if you're on Windows you'd probably need to get this in your path first (or a curl for Windows, of course). On Linux it's almost always built-in (if you don't have it on Linux, just apt-get it). I never use Hoogle from the command-line by the way, it's simply not as accessible, but it can still be very helpful because some text-editors and their plugins can take advantage of it.

Alternatively you can use FPComplete's Hoogle which is sometimes more satisfying (because in my experience it has been aware of more 3rd party libraries. I only use it in those "Hoogling sessions").

There is also Hayoo! by the way.

1 In Hoogle you probably >95% of the time won't have to do this but +Module to import a module if for some reason it's not being searched for (which is the case sometimes for 3rd party libraries).
You can also filter away modules by -Module.
For instance: destroyTheWorld +World.Destroyer -World.Destroyer.Mercy to find destroyTheWorld and make sure you're not looking at the merciful way to do it (This comes very handy with modules with the same function names for different versions, like the ones in Data.ByteString & Data.ByteString.Lazy, Data.Vector & Data.Vector.Mutable, etc.).

Oh and one more awesome advantage of Hoogle is that not only it shows you the function's signature, it can also take you to the module's Haddock pages, so you also gain documentation + in those pages, when available, you can click on "Source" at the right of every function to see how it's being implemented for even more information.

This is outside the scope of the question, but Hoogle is also used to query for function signatures which is just.. mindblowingly helpful. If I want a function that takes an index number and a list and gives me the element in that index, and I wonder if it's already built in, I can search for it within seconds.
I know that the function takes a number and a list, and gives me an element of the list so the function signature must look something along these lines: Int -> [a] -> a (or generically: Num a => a -> [b] -> b), and both instances show up that there really is a function for that ((!!) and genericIndex).

Where GHCi has the upperhand is that you can toy with the expressions, explore them, etc. A lot of times when dealing with abstract functions that means a lot.
Being able to :l(oad) is very very helpful as-well.

If you're just looking for function signatures, you can combine both Hoogle and GHCi.
In GHCi you can type :! cmd, and GHCi will execute cmd in the command-line, and print the results. That means you can Hoogle inside GHCi too, e.g. :! hoogle void.

Upvotes: 5

Luis Casillas
Luis Casillas

Reputation: 30237

Start ghci, :cd to the base directory of the source you're reading, :load the module you're interested in, and use the :i command to get the info:

ghci> :i <$>
(<$>) :: Functor f => (a -> b) -> f a -> f b
    -- Defined in `Data.Functor'
infixl 4 <$>
ghci> :i $
($) :: (a -> b) -> a -> b   -- Defined in `GHC.Base'
infixr 0 $
ghci> :i .
(.) :: (b -> c) -> (a -> b) -> a -> c   -- Defined in `GHC.Base'
infixr 9 .

That tells you the type, where it's defined, the associativity (infixl or infixr) and the precedence (the number; higher is tighter). So (*10) <$> a $ 1 is read as ((*10) <$> a) $ 1.

When you :load a module, all of the names that are in scope inside that module will be in scope inside ghci. The one place where this can get annoying is if you have an error in the code, then you can't :i anything inside it. In these cases, you can comment lines out, use undefined, and probably also use typed holes as behlkir suggests (haven't played with those too much yet).

While you're at it, try the :? command in ghci.

Upvotes: 3

Related Questions