Haskell 从 do 符号传递到 Applicative

Haskell Passing from do notation to Applicative

我正在尝试从 https://haskell-at-work.com/episodes/2018-01-19-domain-modelling-with-haskell-data-structures.html

中删除 Database.sh 文件中的 do 符号

但是我有一个错误,我不知道为什么。 (可能只是意味着我不知道 Haskell)

Haskell代码:

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)

数据库
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

错误

运行之后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)))
   |     ^^^

这是一个简单的缩进错误。

请记住,let <definitions> in <expr> 的语法允许有一个包含 多个 定义的块。 “多个事物的块”1 基本上始终是 Haskell 中缩进重要的上下文,并且规则始终是块中的每个“事物”都必须开始在同一列 ,如果它们跨越多行,则续行必须比块的对齐列缩进更多

这意味着这很好:

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

因为 fbb 开始 let 块中的每个方程都在同一列中。这个也不错:

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

因为方程式仍然完全一致。他们排列的位置实际上比 let 关键字本身缩进 less 无关紧要,in 关键字更远也无关紧要缩进。

但这很糟糕:

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

因为 let 中的定义块以 rtn = [] 开始 。第一个定义中的所有内容都必须比 rtn 中的 r 缩进得更远。要更正它,您需要类似的东西:

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

您将以下行缩进更多的地方。或者(为了避免过度缩进)做这样的事情:

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

您可以通过多种方式做到这一点;您也可以将 getTransactions 中的 = 放在下一行,或者您可以将 let 留在上一行,只将 rtn = [] 向下移动,等等。但是一次你开始定义块,你必须继续规则,即块中条目的一部分必须进一步缩进;你不能重置 mid-block.


1 do块有多个语句,letwhere块有多个定义,case块有多个分支、模块有多个导入和定义(通常我们将它们对齐在缩进级别 0,但你不必这样做),等等

其他任何缩进重要,纯粹是约定俗成和可读性的问题(if/then/elsein部分let/in、守卫或函数定义的其他部分等)