我正在玩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
的调用。
我不确定我做错了什么。有任何想法吗?我应该尝试一种完全不同的方法来进行这个项目吗?
提前致谢。
答案 0 :(得分:6)
问题在于声明run
的方式。
run :: (Runnable a) => d -> Map String a -> Either [String] Integer
你可能想要的是第二个参数是从Map
到任何可运行的String
,在同一个地图中混合在一起。但是实际意味着第二个参数是Map
从String
到一种特定的类型的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
未满足此要求:如果d
为Block 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
有两个不同的类型变量a
和d
;他们唯一的限制是他们都在Runnable
。这意味着该函数必须适用于任何对可运行类型a
和d
。但是,对于块,这没有意义 - 除了Block a
之外,你不能运行Map String a
,因为你进行了联合操作。因此,run
的实施不像签名所希望的那样通用 - 您的实现意味着a
中的Block a
与不同{ em> a1
中的变量Map String a1
,但run
的类型使您无法强制执行此操作。
您实际上可以使用多参数类型类修复此问题,并确保a
中的Block a
必须与Map
显式的类型相同。事实上,这可能是一个很好的学习练习,以了解多参数类型类(这正是他们听起来像,但也非常酷)。但是,最好的解决方案是重写代码,不要在这里使用类型类 - 使用代数dtta类型代替你的抽象语法。这是在Haskell中表示抽象语法的最常用和最方便的方法。