2017-09-18 94 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因爲解決它實際上改善了的代碼。