2017-09-18 58 views
4

虽然例子是人为的,为什么我不能使用通配符模式,如果该数据构造被忽略?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) 
+0

为什么不将它匹配到一个变量,然后使用'name'就可以了? 'personToString p = name p' – arrowd

+0

当然可以,但我正在使用这个人为的例子来理解发生了什么...... –

回答

6

我不确定编译器为什么不能推断这两个和类型都有{ name :: String }作为参数。我不认为编译器现在可以这样做,我不确定这甚至是可能的。

话虽如此,有方法可以反思您使用的类型,并且您可以定义personToString函数,以便它可以在您的Person类型上工作。请记住,这正在研究更先进的语言领域,这也是我的一个新领域。这可能超出了你的问题,但它可能对其他人有帮助,而且知道什么是可能的是很好的。

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

class DoesHaveName a where 
    getName :: a -> String 

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

import Data.Generic.Rep (class Generic) 

derive instance genericPerson :: Generic Person _ 

我们可以看到所有的不同的方式在Data.Generic.Rep看构造函数来表示类型,我们可以通过from变换Person成结构。

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

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

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

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将符号转换为我们可以匹配的字符串。如果标签是“名称”,那么我们将返回字段中的值,否则我们将返回“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

0

如果构造可以安全地被忽略,这是一个气味的类型可以重构:

data AmyOrGeorge = Amy | George 

data Person = Person AmyOrGeorge { name :: String } 

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

我同意语言设计师的选择离开这个fe因为解决它实际上改善了的代码。