繁体   English   中英

了解 haskell 中的循环/递归数据类型

[英]understanding cyclic/recursive data types in haskell

我试图完全理解 haskell 的数据类型,所以我创建了这些:

data District = District {nameOfCity :: String, 
                          subDistricts ::[DistrictUnit]} 
                          deriving (Show, Eq)

data DistrictUnit = DistrictUnit District 
                    | BranchUnit Branch
                    deriving (Show, Eq)

data Branch = Branch {nameOfBranch :: String,
                      numOfEmployees :: Int,
                      sales :: Int}
                      deriving (Show, Eq)

这里有些例子:

a = District {nameOfCity = "Berlin", subDistrcits = []}

b = District {nameOfCity = "New York", 
             subDistricts = 
             [DistrictUnit
             (District {nameOfCity = "Amsterdam",
             subDistricts = 
             [DistrictUnit
             (District {nameOfCity = "London",
             subDistricts = []})]})]}

c = District {nameOfCity = "Iowa" , 
             subDistricts = 
             [BranchUnit
             (Branch {nameOfBranch = "Omaha",            
             numOfEmployees = 3, 
             sales = 2343})]}

现在我正在尝试构建两个函数:getNameOfCity 和 getNameOfBranch:

getNameOfCity b -> ["New York", "Amsterdam", "London"]

但我有大量的模式匹配问题。 我只是不知道如何构建适用于所有输入的 function。

这是我的尝试:

getNameOfCity :: District -> [String]
getNameOfCity (District a b) = [a]

这个 function 只会给我名字:

["New York"]

所以我尝试了这个:

getNameOfCity (District a [DistrictUnit (District b c)]) = [a, b] 

而且(当然)那个人只会给我前两个名字:

["New York", "Amsterdam"]

如何使 getNameOfCity 适用于所有输入? getNamesOfBranch 是否会以同样的方式工作(因为它有不同的参数)? 先感谢您:)

与其尝试使用单个 function 做所有事情,不如编写单独的帮助程序更容易:

allDistrictNames :: District -> [String]  -- This was your `getNameOfCity`
allDistrictNames_u :: DistrictUnit -> [String]
allDistrictNames_ul :: [DistrictUnit] -> [String]

allDistrictNamesallDistrictNames_u现在可以通过简单的模式匹配和相互调用来实现。 allDistrictNames_ul需要对所有列表元素调用allDistrictNames_u ,并合并结果。 一个标准的 function 用于这个确切的目的

您希望getNameOfCity应用于District以返回其nameOfCity字段,该字段位于其每个 subDistricts 的城市名称列表之前,这些subDistrictsDistrict s 而不是Branch es。

所以你有一个很好的起点,如下所示:

getNameOfCity :: District -> [String]
getNameOfCity (District name units) = [name]

但是当然你需要对units做一些事情。 您在第二个示例中编写的模式[DistrictUnit (District bc)]仅在subDistricts字段包含恰好一个元素的列表时匹配,其中该元素也是DistrictUnit ,并且仅从中提取nameOfCity字段( b )区,忽略任何进一步的嵌套分区(在c中)。

为了帮助您解决这个问题,我将为您提供一个模板供您填写,遵循一个常见模式,这在您熟悉 Haskell 时非常有用:从期望的结果返回,将问题分解为小问题步骤,并给每个步骤一个名称和类型签名。 这样,如果您犯了错误,编译器将生成更易于理解的错误消息。 然后,一旦有了可行的解决方案,就可以删除中间变量并将步骤合并为更紧凑的表达式。

首先,您想要的结果是附加在所有分区名称列表中的根区名称。

getNameOfCity :: District -> [String]
getNameOfCity (District name units) = let

  allCities :: [String]
  allCities = …

  in name : allCities

要构建它,您需要遍历units并从每个区域中提取名称,然后连接所有结果。

getNameOfCity :: District -> [String]
getNameOfCity (District name units) = let

  districtCities :: [[String]]
  districtCities = …  -- List of list of city names for each ‘District’

  allCities :: [String]
  allCities = …  -- Concatenated ‘districtCities’

  in name : allCities

可以通过几种方式对单元进行迭代,但我要做的是首先仅提取District s,这样您就可以使用更窄、更精确的类型,然后您可以简单地递归地应用getNameOfCity

getNameOfCity :: District -> [String]
getNameOfCity (District name units) = let

  -- Extract only those ‘units’ that are ‘District’s
  districts :: [District]
  districts = …
    -- Hint:
    --   Hoogle ‘(a -> Maybe b) -> [a] -> [b]’
    --   and define ‘getDistrict :: DistrictUnit -> Maybe District’;
    --   or use a list comprehension of the form: ‘[… | … <- units]’

  districtCities :: [[String]]
  districtCities = …

  allCities :: [String]
  allCities = …

  in name : allCities

您可以对分支名称采用非常相似的方法; 这是一个很好的练习,一旦你把它们都放在你面前,考虑如何抽象出它们的共性。


这也是使用SemigroupMonoid类型类解决此类问题的常见高级方法,它们是可以以某种方式关联地“附加”的类型集的数学名称( <>运算符),并且具有默认的“空”值( mempty ),它是用于追加的无操作。 一般的方法是将map每个值转换为一个“摘要”值——这里是城市名称列表——然后将这些摘要fold在一起,方便地组合成一个称为foldMap的操作。 例如:

districtCities :: District -> [String]
districtCities (District city units) = [city] <> foldMap unitCities units

unitCities :: DistrictUnit -> [String]

-- Recursively summarise district.
unitCities (DistrictUnit district) = districtCities district

-- Return “empty” value for branch.
unitCities (BranchUnit branch) = mempty

这概括为累积其他字段,例如分支名称:

districtBranches :: District -> [String]
districtBranches (District _ units) = foldMap unitBranches units

unitBranches :: DistrictUnit -> [String]
unitBranches (DistrictUnit district) = districtBranches district
unitBranches (BranchUnit branch) = [nameOfBranch branch]

或其他类型的值,如总销售额,使用Sum幺半群:

import Data.Monoid (Sum(..))

totalSales :: District -> Int
totalSales district = getSum (districtSales district)
-- Or: totalSales = getSum . districtSales

districtSales :: District -> Sum Int
districtSales (District _ units) = foldMap unitSales units

unitSales :: DistrictUnit -> Sum Int
unitSales (DistrictUnit district) = foldMap districtSales district
unitSales (BranchUnit branch) = Sum (branchSales branch)

甚至一次使用多个值,例如: (Sum 1, ["London"]) <> (Sum 2, ["Paris"]) = (Sum 3, ["London", "Paris"]) 不过,请注意所有这些实现如何也具有相似的结构,并考虑如何对这种“折叠”模式进行一般抽象。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM