Purescript:模式匹配通配符数据构造函数

时间:2017-09-18 01:00:39

标签: haskell purescript

虽然这个例子是设计的,但如果忽略数据构造函数,为什么我不能使用通配符模式呢?

module Main where

import Prelude
import Control.Monad.Eff.Console (log)

data Person = Amy { name :: String  } | George { name :: String  }

--Implementations Options Below

main = log $ personToString $ George  { name: "George" }

没有错误

personToString :: Person -> String
personToString (Amy { name: n }) = n
personToString (George { name: n }) = n

错误

personToString :: Person -> String
personToString (_ { name: n }) = n

http://try.purescript.org/?session=a1503b9a-0546-7832-39b0-6321a89ef2e3

Unable to parse module:
  unexpected {
  expecting ::, operator or )

2 个答案:

答案 0 :(得分:6)

我不确定为什么编译器无法推断两种和类型都有{ name :: String }作为参数。我不认为编译器现在可以做到这一点,我不确定它是否可能。

话虽如此,有一些方法可以反省您使用的类型,您可以定义personToString函数,以便它可以在Person类型上运行。请记住,这是深入研究语言的更高级区域,这对我来说也是一个新领域。这可能超出了你的问题范围,但它可能对其他人有所帮助,并且知道什么是可能的很好。

首先,让我们为“具有名称的类型”定义类型类。

class DoesHaveName a where
  getName :: a -> String

现在我们需要检查Person类型的结构。为此,我们可以使用purescript-generics-rep包。首先,我们将告诉编译器检查数据类型并创建它的通用表示。我们将为Generic类型创建Person的实例。

import Data.Generic.Rep (class Generic)

derive instance genericPerson :: Generic Person _

我们可以通过查看Data.Generic.Rep中的构造函数来查看表示类型的所有不同方法,我们可以使用fromPerson转换为该结构。

import Data.Generic.Rep (class Generic, from)

personToString :: Person -> String
personToString a = getName (from a)

所以现在我们必须为任何接受DoesHaveName的单参数构造函数创建{ name :: String }的实例。

import Data.Generic.Rep (class Generic, to, from, Sum(..), Rec(..), NoConstructors, Constructor(..), Field(..))
import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol)

instance doesHaveNameConstructor
  :: (IsSymbol t0, IsSymbol t1)
  => DoesHaveName (Constructor t0 (Rec (Field t1 String))) where
  getName (Constructor (Rec (Field c))) =
    case (reflectSymbol (SProxy :: SProxy t1)) of
      "name" -> c
      _ -> "NoName"

要咀嚼很多东西。我会尽力把它分解。 t0t1是符号 - 因此它们是您编写的文字代码的一部分。在这种情况下,t0是Sum类型构造函数的名称(Amy或George)。 t1是记录的标签(在您的示例中,它将是“名称”)。因此,我们使用reflectSymbol将符号转换为我们可以匹配的字符串。如果标签是“name”,那么我们将返回字段内的值,否则我们将返回“NoName”。

我们需要做的最后一件事是为Sum类型结构创建一个DoesHaveName实例。 Sum类型包含构造函数,因此这个实例基本上只是处理外部结构并委托给我们在上面定义的实例。

instance doesHaveNameSum
  :: (DoesHaveName a, DoesHaveName b)
  => DoesHaveName (Sum a b) where
  getName (Inl a) = getName a
  getName (Inr b) = getName b

现在我们可以记录各种人的名字......

data Person
  = Amy { name :: String  }
  | George { name :: String  }
  | Jim { name :: String  }


-- Logs "amy"
log $ personToString (Amy { name: "amy" }

-- Logs "george"
log $ personToString (George { name: "george" }

-- Logs "jim"
log $ personToString (Jim { name: "jim" }

演示:http://try.purescript.org/?gist=2fc95ad13963e96dd2a49b41f5703e21

答案 1 :(得分:0)

如果可以安全地忽略构造函数,那就是可以重构类型的气味:

data AmyOrGeorge = Amy | George

data Person = Person AmyOrGeorge { name :: String  }

personToString (Person _ { name: n }) = n

我同意语言设计师的意见。选择退出此功能,因为解决它实际上改进了代码。