2017-09-18 3 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

컴파일러가 sum 유형 모두가 { name :: String }을 인수로 갖고 있다고 추론 할 수없는 이유는 정확히 모르겠습니다. 나는 컴파일러가 지금 당장 그렇게 할 수 있다고 생각하지 않으며, 심지어 가능하다고 확신하지 못한다.

그렇다고해서 사용하는 유형을 내성적으로 검사 할 수있는 방법이 있으며 함수를 정의하여 Person 유형에서 작동 할 수 있습니다. 이것은 언어의 고급 영역을 탐구하고 있으며 이것은 또한 새로운 영역입니다. 이것은 아마도 여러분의 질문을 넘어서는 길일 것입니다. 그러나 다른 사람들에게 도움이 될 수 있으며, 가능한 것이 무엇인지 아는 것이 좋습니다.

먼저 "이름이있는 유형"에 대한 typecass를 정의 해 보겠습니다.

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은 레코드 레이블입니다 (예제에서 "name"이됩니다). 따라서 우리는 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 

나는이 남겨두고 언어 디자이너의 선택에 동의 실제로 주위를 일하는 것이 코드를 개선하기 때문에 코드가으로 향상되었습니다.