Home > Software design >  Haskell Passing from do notation to Applicative
Haskell Passing from do notation to Applicative

Time:04-12

I am trying to strip away do notation in the Database.sh file from https://haskell-at-work.com/episodes/2018-01-19-domain-modelling-with-haskell-data-structures.html

But I am having an Error and I have no Idea why. (Probably just means I don't know about Haskell)

Haskell Code:

Project.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Project where

import           Data.Text (Text)

newtype Money = Money
  { unMoney :: Double
  } deriving (Show, Eq, Num)

newtype ProjectId = ProjectId
  { unProjectId :: Int
  } deriving (Show, Eq, Num)

data Project
  = Project ProjectId
            Text
  | ProjectGroup Text
                 [Project]
  deriving (Show, Eq)

data Budget = Budget
  { budgetIncome      :: Money
  , budgetExpenditure :: Money
  } deriving (Show, Eq)

data Transaction
  = Sale Money
  | Purchase Money
  deriving (Eq, Show)

Database
import           System.Random (getStdRandom, randomR)

import Project

getBudget :: ProjectId -> IO Budget
getBudget _ = Budget
    <$> (Money <$> getStdRandom (randomR (0, 10000)))
    <*> (Money <$> getStdRandom (randomR (0, 10000)))

getTransactions :: ProjectId -> IO [Transaction]
getTransactions _ = let rtn = []
    <$>  (Sale . Money <$> getStdRandom (randomR (0, 4000)))
    <*> (Purchase . Money <$> getStdRandom (randomR (0, 4000)))
  in
    pure rtn

Error

After running stack ghc Database.hs --package random

[2 of 2] Compiling Database         ( Database.hs, Database.o )

Database.hs:12:5: error: parse error on input `<$>'
   |
12 |     <$>  (Sale . Money <$> getStdRandom (randomR (0, 4000)))
   |     ^^^

CodePudding user response:

This is a simple indentation error.

Remember that the syntax of let <definitions> in <expr> allows there to be a block of multiple definitions. "A block of multiple things"1 is basically always the context where indentation matters in Haskell, and the rule is always that each of the "things" in the block must start at the same column and if they span more than one line the continuation lines must all be more indented than the alignment column for the block.

That means that this is good:

something = let foo = 1
                bar = 2
                baz = 3
             in foo   bar   baz

Because the f, b, and b starting each of the equations in the let block line up at the same column. This is also good:

something = let
  foo = 1
  bar = 2
  baz = 3
        in foo   bar   baz

Because the equations still line up exactly. It does not matter that the position they line up at is actually less indented than the let keyword itself, nor does it matter that the in keyword is even further indented.

But this is bad:

getTransactions :: ProjectId -> IO [Transaction]
getTransactions _ = let rtn = []
    <$>  (Sale . Money <$> getStdRandom (randomR (0, 4000)))
    <*> (Purchase . Money <$> getStdRandom (randomR (0, 4000)))
  in
    pure rtn

Because the block of definitions inside the let starts with rtn = []. Everything that's part of that first definition has to be indented further than the r from rtn. To correct it you either need something like:

getTransactions :: ProjectId -> IO [Transaction]
getTransactions _ = let rtn = []
                         <$>  (Sale . Money <$> getStdRandom (randomR (0, 4000)))
                         <*> (Purchase . Money <$> getStdRandom (randomR (0, 4000)))
  in
    pure rtn

Where you indent the following lines more. Or (to avoid excessive indentation) do something like this:

getTransactions :: ProjectId -> IO [Transaction]
getTransactions _ =
  let rtn = []
        <$>  (Sale . Money <$> getStdRandom (randomR (0, 4000)))
        <*> (Purchase . Money <$> getStdRandom (randomR (0, 4000)))
  in
    pure rtn

There are various ways you can do this; you could put the = from getTransactions on the next line too, or you could leave the let on the previous line and only move the rtn = [] down, etc. But once you start the block of definitions, you have to continue the rule that everything that's part of an entry in the block must be further indented; you can't reset back out mid-block.


1 do blocks have multiple statements, let and where blocks have multiple definitions, case blocks have multiple branches, modules have multiple imports and definitions (conventionally we align these at indentation level 0, but you don't have to), etc, etc.

The indentation of anything else does not matter, and is purely a matter of convention and readibility (if/then/else, the in part of a let/in, guards or other parts of a function definition, etc)

  • Related