Loading [MathJax]/jax/output/CommonHTML/config.js
社区首页 >问答首页 >Haskell -查找未声明的变量

Haskell -查找未声明的变量
EN

Stack Overflow用户
提问于 2017-10-05 07:09:43
回答 1查看 242关注 0票数 2

AST:

代码语言:javascript
代码运行次数:0
复制
data AST = Nr Int | Sum AST AST | Mul AST AST | Min AST | If AST AST AST | 
Let String AST AST | Var String deriving (Eq, Show)

嗨!我需要帮助在输入中找到未声明的变量。我的问题是,我不能简单地在我的评估器中这样做:

代码语言:javascript
代码运行次数:0
复制
eval :: Env -> AST -> Int
eval env (Nr nr) = nr
eval env (Sum xs xss) = eval env xs + eval env xss
eval env (Mul xs xss) = eval env xs * eval env xss
eval env (Min xs ) = - eval env xs
eval env (If x xs xss) = if (eval env x == 0)
                then eval env xs
                else eval env xss
eval env (Let s xs xss) = eval ((s, (eval env xs)) : env) xss 
eval env (Var x) = case lookup x env of
    Just n -> n
    Nothing -> error ("Variable " ++ x ++ " is undeclared!")

如果有任何未声明的变量,我需要在解析时给出一个包含所有未声明变量的列表的适当错误,或者在计算之前对我的AST进行后处理。我不知道从哪里开始。下面是一个解析表达式的示例:

代码语言:javascript
代码运行次数:0
复制
parse "let X = + 1 2 in * X + 2 - X"
    Let "X" (Sum (Nr 1) (Nr 2)) (Mul (Var "X") (Sum (Nr 2) (Min (Var "X"))))
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-10-05 08:23:17

让我们从类型开始:

如果有任何未声明的变量,我需要在解析时给出一个包含所有未声明变量的列表的适当错误。

如果函数eval给出未声明变量的列表,或者给出一个Int (如果没有未声明的变量),怎么样?

代码语言:javascript
代码运行次数:0
复制
type Identifier = String

eval :: Env -> AST -> Either [Identifier] Int

我们现在需要将原始数字包装在Right中:

代码语言:javascript
代码运行次数:0
复制
eval env (Nr nr) = Right nr

Var情况下,声明的变量也是如此,而未声明的变量被包装在列表和Left中。

代码语言:javascript
代码运行次数:0
复制
eval env (Var x) = case lookup x env of
    Just n -> Right n
    Nothing -> Left [x]

对于Min情况,我们不能再否定递归调用了,因为否定不是为Either [Identifier] Int定义的。

我们可以通过模式匹配来看看我们得到了什么:

代码语言:javascript
代码运行次数:0
复制
eval env (Min xs ) = case eval env xs of
  Left err -> Left err
  Right x  -> Right (-x)

但是,这是非常冗长的,并且与使用来自Either e的函子实例的Either e完全相同:

代码语言:javascript
代码运行次数:0
复制
eval env (Min xs ) = fmap negate (eval env xs)

类似地,对于Sum,我们可以对两个参数进行模式匹配:

代码语言:javascript
代码运行次数:0
复制
eval env (Sum xs xss) = case (eval env xs, eval env xss) of
  (Left err, Left err') -> Left (err ++ err')
  (Left err, Right _)   -> Left err
  (Right _, Left err')  -> Left err'
  (Right a, Right b)    -> Right (a + b)

注意,如果两个子项都包含未声明的变量,那么我们将它们连接起来,以获得Sum下的未声明变量列表。

对于其余的构造函数来说,这也是我们需要的技巧。但是,我不想每次都要键入这样的大型case语句。这是一个小加法的大量工作!IfLet将有8箱!

因此,让我们为我们创建一个帮助函数来完成这个任务:

代码语言:javascript
代码运行次数:0
复制
apply :: Either [Identifier] (a -> b) -> Either [Identifier] a -> Either [Identifier] b
apply (Left err) (Left err') = Left (err ++ err')
apply (Left err) (Right _)   = Left err
apply (Right _)  (Left err') = Left err'
apply (Right f)  (Right a)   = Right (f a)

现在,为SumMulIf定义用例要容易得多:

代码语言:javascript
代码运行次数:0
复制
eval env (Sum xs xss) = fmap (+) (eval env xs) `apply` eval env xss
eval env (Mul xs xss) = fmap (*) (eval env xs) `apply` eval env xss
eval env (If x xs xss) = fmap jnz (eval env x) `apply` eval env xs `apply` eval env xss
  where jnz i a a' = if i == 0 then a else a'

Let略有不同:

代码语言:javascript
代码运行次数:0
复制
eval env (Let s xs xss) = fmap second v `apply` eval env' xss
  where val = eval env xs
        env' = (s,val) : env
        getRight (Right a) = a
        getRight (Left _) = 0
        second _ a = a

注意,当第一个项包含一个未声明的变量时,我们如何“欺骗”第二个术语的环境中的伪值。由于我们不会使用任何Int值,所以在这种情况下,第二个项可能会产生结果,这是可以的。

一旦您深入到Haskell,您可能会注意到,apply看起来非常像来自Applicative<*>。我们之所以不这么做,是因为Either eApplicative实例没有按我们希望的方式工作。它不是聚合错误,而是在碰到第一个错误时退出:

代码语言:javascript
代码运行次数:0
复制
>>> Left ["foo"] `apply` Left ["bar", "baz"]
Left ["foo", "bar", "baz"]
>>> Left ["foo"] <*> Left ["bar", "baz"]
Left ["foo"]

但是,有一个应用程序实例正是以这种方式工作的 package,因此,如果您愿意,可以使用它:

代码语言:javascript
代码运行次数:0
复制
>>> Failure ["foo"] <*> Failure ["bar", "baz"]
Failure ["foo", "bar", "baz"]

一种可能使Let情况不那么麻烦的方法是将eval的返回类型从Either [Identifier] Int更改为([Identifier], [(Identifier, Int)] -> Int) --让它返回表达式中所有空闲变量的列表,以及在给定这些变量绑定的情况下计算表达式的方法。

如果我们给这个类型起个名字:

代码语言:javascript
代码运行次数:0
复制
data Result a = Result { freeVariables :: [Identifier], eval :: [(Identifier,Int)] -> a }

我们可以为它定义FunctorApplicative实例:

代码语言:javascript
代码运行次数:0
复制
instance Functor Result where
  fmap f (Result is g) = Result is (f . g)
instance Applicative Result where
  pure a = Result [] (const a)
  Result is ff <*> js fa = Result (is ++ js) (ff <*> js)

并使用这些方法轻松地定义一个函数来解析空闲变量和eval表达式:

代码语言:javascript
代码运行次数:0
复制
parse :: AST -> Result Int
parse (Nr nr) = pure nr
parse (Sum xs xss) = (+) <$> parse xs <*> parse xss
parse (Mul xs xss) = (*) <$> parse xs <*> parse xss
parse (Min xs ) = negate <$> parse xs
parse (If x xs xss) = jnz <$> parse x <*> parse xs <*> parse xss
  where jnz a b c = if a == 0 then b else c
parse (Let s xs xss) = Result ks h
  where Result is f = parse xs
        Result js g = parse xss
        ks = is ++ delete s js
        h env = g ((s,f env):env)
parse (Var x) = Result [x] $ \env -> case lookup x env of
  Just n -> n
  Nothing -> error ("Variable " ++ x ++ " is undeclared!")
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46589051

复制
相关文章
Haskell doctest
一定要注意格式 第一行很重要,-- |这行没有就不是一个 test。 可以对比 >>> 的个数 和 terminal里的 Examples 个数确认是否自己的所有 test 都测试了
莫听穿林
2022/05/20
3190
Haskell doctest
Haskell
这门语言在数学模型上有着很深的优势,虽然它有很多特性,让人很难接受,随着学习的深入,你才会发现这会多么有趣。
icepy
2019/06/24
8830
JavaScript中的变量查找
众所周知,JavaScript变量是按照作用域链来进行查找的(作用域和作用域链相关知识可参看我的另一篇文章,《基于JavaScript作用域链的性能调优》), 那么,对于一个简单的赋值操作,等号左右两边变量的查找方式一样吗?让我们从一个简单例子讲起~
娜姐
2022/05/13
1.5K0
Haskell Platform安装
不懂了,明天写
云深无际
2020/11/03
1.1K0
Haskell Platform安装
haskell 求助
findBonding :: Eq a => (a -> a -> Bool) -> [a] -> Maybe [(a,a)]
用户6797589
2019/12/02
5530
模块_Haskell笔记2
hiding语法能够缓解命名冲突问题,但不很方便,对于存在大量命名冲突的模块,可以通过qualified保留命名空间来避免冲突
ayqy贾杰
2019/06/12
1.7K0
类型_Haskell笔记3
::读作“类型为”(has type),告诉编译器变量name是String类型(即[Char]类型)的
ayqy贾杰
2019/06/12
9200
类型_Haskell笔记3
本地使用 Haskell CodeWorld Locally
下载好之后注意看下载的版本,因为在之后的 .cabal 文件中要指定 codeworld-api 的版本
莫听穿林
2022/05/20
4990
本地使用 Haskell CodeWorld Locally
ubuntu 14.04 install haskell
haskell是一种函数编程式语言,ghc是Glasgow Haskell Compiler的简称,也是Haskell Platform的基础. 下面是安装ghc的步骤:
pydata
2018/08/02
7290
Monad_Haskell笔记10
从类型来看,Functor到Applicative再到Monad是从一般到特殊的递进过程(Monad是特殊的Applicative,Applicative是特殊的Functor)
ayqy贾杰
2019/06/12
7410
Environments: JS变量查找的“罪魁祸首”
在我们之前的文章中,不管是讲作用域、闭包还是全局变量。Environment都作为关键部分,出现好多次。所以,今天我们从多方面来讨论一下Environment。(有些知识点可能在前面的文章中涉及到)
前端柒八九
2022/08/25
6480
Environments: JS变量查找的“罪魁祸首”
【说站】python变量名的查找方法
1、查找变量名由内而外,分别是Local、Enclosing、Global、Builtin。
很酷的站长
2022/11/26
9220
【说站】python变量名的查找方法
learn-haskell
引言 Haskell不同于Scala,是一门纯函数式语言,它强制使用者使用函数式语法而没有妥协。 是一门强类型定义的静态类型语言。它的**类型模型基于推断理论(in-ferred)**并被公认为是函数语言中最高效的类型系统之一。你会发现该类型系统支持多态语义并有助于人们作出十分整洁清晰的设计。 支持Erlang风格的模式匹配(pattern matching)和哨兵表达式。你也能在Haskell中发现Clojure风格的惰性求值(lazyevaluation)以及与Clojure和Erlang相同的列表推导
刘笑江
2018/05/28
1.1K0
Zipper_Haskell笔记13
数据结构不可变,所以对其进行增、删、改等操作的结果只能是重新创建一份新的数据结构,例如:
ayqy贾杰
2019/06/12
5190
Zipper_Haskell笔记13
铁定不纯的IO_Haskell笔记5
一直有个疑惑,Haskell号称纯函数式语言,那么铁定不纯的场景(肯定有副作用,或者操作本身就是副作用)如何解决?
ayqy贾杰
2019/06/12
1.3K0
函数curry化(Haskell Curry)
当一个函数fn有多个参数时,可以先传入一部分参数,生成一个中继函数nextFn,然后在nextFn当中再传入剩下的参数。(一步curry化)
elson
2020/01/02
1.3K0
基础语法_Haskell笔记1
Haskell是一种纯函数式语言(purely functional programming language),其函数式特性的纯度没有争议
ayqy贾杰
2019/06/12
1.9K0
newtype_Haskell笔记8
在List场景,xs <*> ys表示从左侧xs中取出函数作用于右侧ys中的每一项,有两种实现方式:
ayqy贾杰
2019/06/12
6210
从素数生成看Haskell的简洁性
最近有空就在看Haskell,真是越看越觉得这个语言有意思。在知乎(原回答@阅千人而惜知己的)找到了一份很有意思的求素数代码,非常简洁,我觉得很能体现这个语言的特点。
KAAAsS
2022/01/14
3370
深入typeclass_Haskell笔记4
OOP中的Class是对象模板,用来描述现实事物,并封装其内部状态。FP中没有内部状态一说,所以Class在函数式上下文指的就是接口。派生自某类(deriving (SomeTypeclass))是说具有某类定义的行为,相当于OOP中的实现了某个接口,所以具有接口定义的行为
ayqy贾杰
2019/06/12
5110

相似问题

在MethodDeclaration Eclipse中查找未声明的变量

12

未声明的变量

25

变量未声明

27

未声明绑定变量

20

未声明的变量mysqli

11
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文