Haskell中的迷你编程语言

时间:2012-09-08 15:42:08

标签: haskell semantics

我正在玩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] a作为run的结果。 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的调用。

我不确定我做错了什么。有任何想法吗?我应该尝试一种完全不同的方法来进行这个项目吗?

提前致谢。

3 个答案:

答案 0 :(得分:6)

问题在于声明run的方式。

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

你可能想要的是第二个参数是从Map任何可运行的String,在同一个地图中混合在一起。但是实际意味着第二个参数是MapString一种特定的类型的runnable(它只是不知道它是哪一个)是)。

不是使用类型类和不同类型,而是尝试使用单一类型。

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

答案 1 :(得分: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

不同之处在于,您的班级承诺,d给定run适用于任何可运行的a。但xrun未满足此要求:如果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可以包含不同类型的runnable。这是完整的解决方案:

{-# 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 :(得分:2)

我认为问题在于run的签名。

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

特别是,run有两个不同的类型变量ad;他们唯一的限制是他们都在Runnable。这意味着该函数必须适用于任何对可运行类型ad。但是,对于块,这没有意义 - 除了Block a之外,你不能运行Map String a,因为你进行了联合操作。因此,run的实施不像签名所希望的那样通用 - 您的实现意味着a中的Block a不同 a1中的变量Map String a1,但run的类型使您无法强制执行此操作。

您实际上可以使用多参数类型类修复此问题,并确保a中的Block a必须与Map显式的类型相同。事实上,这可能是一个很好的学习练习,以了解多参数类型类(这正是他们听起来像,但也非常酷)。但是,最好的解决方案是重写代码,不要在这里使用类型类 - 使用代数dtta类型代替你的抽象语法。这是在Haskell中表示抽象语法的最常用和最方便的方法。