2012-09-08 46 views
1

我在與Haskell玩耍,我想我會嘗試用它創建一個簡單的編程語言。現在忽略具體的語法;我專注於抽象語法和語義。Haskell中的迷你編程語言

該語言目前應該由整數,整數加法,變量名稱和變量綁定塊組成。

如果使用的變量不在其使用的範圍內,則會引發錯誤。

以下是我目前的進度:

module ProgLang where 
import Data.Map as Map 

-- Classes 
class Runnable d where 
    run :: (Runnable a) => d -> Map String a -> Either [String] Integer 

-- Data 
data Name = Name String 
    deriving (Eq, Ord, Show) 

data Add a b = Add a b 
    deriving (Eq, Ord, Show) 

data Block a = Block (Map String a) a 
    deriving (Eq, Ord, Show) 

-- Instances 
-- Integers resolve to Right Integer 
instance Runnable Integer where 
    run v _ = Right v 

-- For Names 
-- look up their expression in the scope, then evaluate 
-- if name is out of scope, raise an error 
instance Runnable Name where 
    run (Name n) s = which (Map.lookup n s) where 
    which Nothing = Left ["Variable not in scope: " ++ n] 
    which (Just v) = run v s 

-- For Addition 
-- Run a, Run b, Add their results 
-- Raise appropriate errors where necessary 
instance (Runnable a, Show a, Runnable b, Show b) => Runnable (Add a b) where 
    run (Add a b) s = geta (run a s) where 
    geta (Left es) = Left (es ++ ["In lhs of expression: " ++ show (Add a b)]) 
    geta (Right a') = getb a' (run b s) 
    getb _ (Left es) = Left (es ++ ["In rhs of expression: " ++ show (Add a b)]) 
    getb a' (Right b') = Right (a' + b') 

-- For Blocks 
-- Run the block's expression under a new scope 
--  (merging the current with the block's scope definition) 
instance Runnable a => Runnable (Block a) where 
    run (Block s' e) s = result $ run e (Map.union s' s) where 
    result (Left es) = Left (es ++ ["In block: " ++ show (Block s' e)]) 
    result (Right v) = Right v 

我使用(Runnable a) => Either [String] arun結果。對於錯誤爲Left,對於有效結果爲Right

下面是示例表達式和他們的預期結果:

-- run 5 Map.empty 
-- => Right 5 

-- run (Name "a") Map.empty 
-- => Left ["Variable not in scope: a"] 

-- run (Name "a") (fromList [("a", 6)]) 
-- => Right 6 

-- run (Add 6 3) Map.empty 
-- => Right 9 

-- run (Add (Name "a") 7) (fromList [("a", 10)]) 
-- => Right 17 

-- run (Block (fromList [("a", 10)]) (Name "a")) Map.empty 
-- => Right 10 

我正在從GHCI以下錯誤(7.4.1版本):

progLang.hs:45:53: 
    Could not deduce (a1 ~ a) 
    from the context (Runnable a) 
     bound by the instance declaration at progLang.hs:44:10-41 
    or from (Runnable a1) 
     bound by the type signature for 
       run :: Runnable a1 => 
         Block a -> Map String a1 -> Either [String] Integer 
     at progLang.hs:(45,3)-(47,30) 
     `a1' is a rigid type variable bound by 
      the type signature for 
      run :: Runnable a1 => 
        Block a -> Map String a1 -> Either [String] Integer 
      at progLang.hs:45:3 
     `a' is a rigid type variable bound by 
      the instance declaration at progLang.hs:44:19 
    Expected type: Map String a1 
     Actual type: Map String a 
    In the second argument of `union', namely `s' 
    In the second argument of `run', namely `(union s' s)' 
Failed, modules loaded: none. 

此錯誤(據我可以說)是由於Block的運行功能。它似乎不喜歡撥打Map.union

我不確定我做錯了什麼。有任何想法嗎?我應該嘗試一種完全不同的方法來完成這個項目嗎?

在此先感謝。

回答

6

的問題是,run聲明的方式。

run :: (Runnable a) => d -> Map String a -> Either [String] Integer 

你可能打算是什麼,第二個參數是MapString任何可運行,在同一地圖混爲一談。但是這個實際上是意味着第二個參數是從StringMap一個特定的類型的可運行的(它只是不知道它是什麼)。

而不是使用類型類型和不同類型,請嘗試使用單一類型。

module ProgLang where 
import Data.Map as Map 

data Runnable 
    = Name String 
    | Add Runnable Runnable 
    | Block (Map String Runnable) Runnable 
    | I Integer 
    deriving (Eq, Ord, Show) 

run :: Runnable -> Map String Runnable -> Either [String] Integer 
-- Instances 
-- Integers resolve to Right Integer 
run (I v) _ = Right v 
-- For Names 
-- look up their expression in the scope, then evaluate 
-- if name is out of scope, raise an error 
run (Name n) s = which (Map.lookup n s) where 
    which Nothing = Left ["Variable not in scope: " ++ n] 
    which (Just v) = run v s 
-- For Addition 
-- Run a, Run b, Add their results 
-- Raise appropriate errors where necessary 
run (Add a b) s = geta (run a s) where 
    geta (Left es) = Left (es ++ ["In lhs of expression: " ++ show (Add a b)]) 
    geta (Right a') = getb a' (run b s) 
    getb _ (Left es) = Left (es ++ ["In rhs of expression: " ++ show (Add a b)]) 
    getb a' (Right b') = Right (a' + b') 
-- For Blocks 
-- Run the block's expression under a new scope 
--  (merging the current with the block's scope definition) 
run (Block s' e) s = result $ run e (Map.union s' s) where 
    result (Left es) = Left (es ++ ["In block: " ++ show (Block s' e)]) 
    result (Right v) = Right v 

我這個代碼所做的唯一變化是類型聲明和run功能的重組。

如果您添加一個虛擬Num實例與fromInteger = I那麼您還可以使用整數文字Runnable s。以下是您提供的測試用例的測試運行,看起來像所有預期的輸出匹配:http://ideone.com/9UbC5

2

您錯過了Show a約束。如果你把你的run外實例聲明(我把它改名爲xrun)的這樣

xrun (Block s' e) s = result $ run e (Map.union s' s) where 
    result (Left es) = Left (es ++ ["In block: " ++ show (Block s' e)]) 
    result (Right v) = Right v 

ghci

*ProgLang> :t xrun 
xrun 
    :: (Show a, Runnable a) => 
    Block a -> Map String a -> Either [[Char]] Integer 

但是這並不足以修正約束。並排(一類的聲明和實際類型的xrun將兩種類型的邊:

run ::   (Runnable a) => d  -> Map String a -> Either [String] Integer 
xrun :: (Show a, Runnable a) => Block a -> Map String a -> Either [String] Integer 

所不同的是,鑑於drun應該對任何可運行a的工作,但你的類的承諾xrun不fullfill。這個要求:如果dBlock Int,不能任意a工作,但只有a :: Int

所以其他評論者說,你可能需要改變你的類聲明的一個方法可能是存在的類型:

data AnyRunnable = forall a . (Runnable a) => AnyRunnable a 

class Runnable d where 
    run :: d -> Map String AnyRunnable -> Either [String] Integer 

這是一個不同的合同:現在Map可以包含不同類型的可運行參數。以下是完整的解決方案:

{-# LANGUAGE ExistentialQuantification #-} 
module ProgLang where 
import Data.Map as Map 

data AnyRunnable = forall a . (Runnable a) => AnyRunnable a 

instance Show AnyRunnable where 
    show (AnyRunnable a) = show a 

instance Runnable AnyRunnable where 
    run (AnyRunnable a) = run a 

-- Classes 
class Show d => Runnable d where 
    run :: d -> Map String AnyRunnable -> Either [String] Integer 

-- Data 
data Name = Name String 
    deriving (Show) 

data Add a b = Add a b 
    deriving (Show) 

data Block a = Block (Map String AnyRunnable) a 
    deriving (Show) 

-- Instances 
-- Integers resolve to Right Integer 
instance Runnable Integer where 
    run v _ = Right v 


-- For Names 
-- look up their expression in the scope, then evaluate 
-- if name is out of scope, raise an error 
instance Runnable Name where 
    run (Name n) s = which (Map.lookup n s) where 
    which Nothing = Left ["Variable not in scope: " ++ n] 
    which (Just v) = run v s 

-- For Addition 
-- Run a, Run b, Add their results 
-- Raise appropriate errors where necessary 
instance (Runnable a, Show a, Runnable b, Show b) => Runnable (Add a b) where 
    run (Add a b) s = geta (run a s) where 
    geta (Left es) = Left (es ++ ["In lhs of expression: " ++ show (Add a b)]) 
    geta (Right a') = getb a' (run b s) 
    getb _ (Left es) = Left (es ++ ["In rhs of expression: " ++ show (Add a b)]) 
    getb a' (Right b') = Right (a' + b') 

-- For Blocks 
-- Run the block's expression under a new scope 
--  (merging the current with the block's scope definition) 
instance Runnable a => Runnable (Block a) where 
    run (Block s' e) s = result $ run e (Map.union s' s) where 
    result (Left es) = Left (es ++ ["In block: " ++ show (Block s' e)]) 
    result (Right v) = Right v 

測試是這樣的:

run (Block (fromList [("a", AnyRunnable 10)]) (Name "a")) Map.empty 
2

我認爲問題出在run的簽名。

class Runnable d where 
    run :: Runnable a => d -> Map String a -> Either [String] Integer 

特別地,run具有兩個不同類型變量ad;對他們唯一的限制是他們都在Runnable。這意味着該功能必須適用於任意一對可運行類型ad。但是,對於塊,這沒有任何意義 - 因爲您進行了聯合操作,所以除了​​之外,您無法運行Block a。因此,實施以供run不作爲類型簽名希望它是作爲一般 - 你的實現意味着在Block aa是相同Map String a1不同變量a1,但run類型讓你沒辦法強制執行此操作。

實際上,你可以用多參數的類型類解決這個問題,使事實Block aa必須是一樣的Map明確的類型。事實上,這可能是一個很好的學習練習來理解多參數類型類(這正是它們聽起來像但也非常酷)。但是,最好的解決方案是重寫你的代碼,不要在這裏使用類型類型 - 用代數dtta類型代替抽象語法。這是在Haskell中表示抽象語法的最常見和最方便的方式。