首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >函数的Haskell作用域

函数的Haskell作用域
EN

Stack Overflow用户
提问于 2020-12-23 23:10:57
回答 2查看 112关注 0票数 2

我正在尝试执行一些操作,这些操作将作为在“where”子句之后定义的输入函数,并输出一个由它组成的字符串。这里提到的是将“where”子句后面列出的函数范围扩展到writeOut函数的内容。我想要的东西(不起作用):

代码语言:javascript
复制
writeOut :: [Char] -> [Char]
writeOut x = x
 where
 number :: Char -> [Char]
 number n = [n]
 multiplication :: [Char] -> [Char] -> [Char]
 multiplication a b = a++"*"++b
 addition :: [Char] -> [Char] -> [Char]
 addition a b = "("++a++"+"++b++")"
 subtraction :: [Char] -> [Char] -> [Char]
 subtraction a b = "("++a++"-"++b++")"
 ...

因此,例如,对于输入:

代码语言:javascript
复制
writeOut multiplication (addition (number '1') (subtraction (number '2') (number '3'))) (number '3')

会给我一个输出:

代码语言:javascript
复制
(1+(2-3))*3

当我删除writeOut函数并保留“where”关键字之后的内容时,它正常工作,但是我真的想在'writeOut‘函数下“锁定它”。

您可能会问我为什么要这样做,因为我想要另一个函数来为我计算表达式:

代码语言:javascript
复制
writeOut multiplication (addition (number '1') (subtraction (number '2') (number '3'))) (number '3')

Output: (1+(2-3))*3

calc multiplication (addition (number '1') (subtraction (number '2') (number '3'))) (number '3')

Ouput: 0

以防万一你想看看什么是“正常”的:

代码语言:javascript
复制
number :: Char -> [Char]
number n = [n]
multiplication :: [Char] -> [Char] -> [Char]
multiplication a b = a++"*"++b
addition :: [Char] -> [Char] -> [Char]
addition a b = "("++a++"+"++b++")"
subtraction :: [Char] -> [Char] -> [Char]
subtraction a b = "("++a++"-"++b++")"
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-12-23 23:31:04

您似乎正在尝试为该DSL实现DSL和评估器。而不是一组函数,定义一个数据类型,然后为该类型编写一个评估器。

代码语言:javascript
复制
data Expr = Number Int
          | Mult Expr Expr
          | Add Expr Expr
          | Subtract Expr Expr


writeOut :: Expr -> String
writeOut (Number x) = show x
writeOut (Add x y) = "(" ++  writeOut x ++ " + " ++ writeOut y ++ ")"
writeOut (Subtract x y) = "(" ++  writeOut x ++ " - " ++ writeOut y ++ ")"
writeOut (Mult x y) = "(" ++ writeOut x ++ " * " ++ writeOut y ++ ")"


print $ writeOut (Mult (Add (Number 1) (Subtract (Number 2) (Number 3))) (Number 3)

我把它作为消除最外层括号的一个练习。

作为额外的分配,定义一个不同的评估器来计算表达式到单个Int,而不是显示它:

代码语言:javascript
复制
evaluate :: Expr -> Int
evaluate = undefined
票数 5
EN

Stack Overflow用户

发布于 2020-12-24 00:06:06

目前,您对numbermultiplicationaddition等的定义都是在字符串或字符上操作的函数。因此,即使假设它们是在顶层定义的,所以您也可以在这样的表达式中引用它们:

代码语言:javascript
复制
writeOut multiplication (addition (number '1') (subtraction (number '2') (number '3'))) (number '3')

这意味着writeOut必须能够接受函数multiplication作为它的第一个参数,(addition …)的结果作为它的第二个,(number …)的结果作为它的第三个,所以它需要一个类似([Char] -> [Char] -> [Char]) -> [Char] -> [Char] -> [Char]的类型。以这种方式重载writeOut并允许任何其他表达式作为参数是不可能的;如果您尝试使用类型类型来实现它,您将很快发现它会产生许多不明确的类型,因此需要大量类型注释。

但是有许多更好的方法,取决于你在这里真正想要完成的是什么。最简单的方法是将这些函数移到顶层:

代码语言:javascript
复制
writeOut :: [Char] -> [Char]
writeOut x = x

number :: Char -> [Char]
number n = [n]

multiplication :: [Char] -> [Char] -> [Char]
multiplication a b = a++"*"++b

…

并使用括在括号中的表达式像writeOut一样调用writeOut (multiplication (number '2') (number '3'))。但是在这里,writeOut是完全多余的:它等价于恒等函数id,它只是返回它的参数而没有修改。构造字符串的所有实际工作都由其他函数完成。

这就提出了一个要点:这些函数并不是真正构建表达式,而是构建一个字符串,并丢弃有关表达式结构的信息。如果您还希望能够使用calc函数计算表达式的值,则需要在某个地方对此进行概括。

有两种典型的方法可以做到这一点。

一种方法是创建用于表示表达式的数据类型,然后您的两个函数writeOutcalc将使用该类型的值,并分别生成一个字符串或数字:

代码语言:javascript
复制
-- An expression is:
data Expression

  -- A literal number, containing an ‘Int’ (or whichever type you need);
  = Number Int

  -- Or an operation on some subexpressions.
  | Addition Expression Expression
  | Multiplication Expression Expression
  | Subtraction Expression Expression
  | …

  -- You can add this clause to allow ‘Expression’ to be displayed
  -- as Haskell code for debugging purposes in GHCi. Generally you
  -- should derive ‘Show’ instead of making your own instance.
  --
  -- deriving (Show)

writeOut :: Expression -> String
writeOut expression = case expression of
  Number n -> show n
  Addition a b -> concat ["(", writeOut a, " + ", writeOut b, ")"]
  …

calc :: Expression -> Int
calc expression = case expression of
  Number n -> n
  Addition a b -> calc a + calc b
  …

然后,您可以构造一个表达式,并分别将其转换为字符串、数字或其他需要的内容,例如在GHCi中:

代码语言:javascript
复制
> :type Number
Number :: Int -> Expression

> :type Multiplication
Multiplication :: Expression -> Expression -> Expression

> example = Multiplication (Addition (Number 1) (Subtraction (Number 2) (Number 3))) (Number 3)

> :type example
example :: Expression

> writeOut example
"((1+(2-3))*3)"

> calc example
0

> calc (Plus (Number 1) (Number 2))
3

注意,您仍然需要writeOutcalc参数周围的括号;如果您编写了writeOut Plus (Number 1) (Number 2),这将意味着用三个参数调用writeOut,即PlusNumber 1Number 2,这不是您想要的。

如果不想使用大写构造函数名,则可以创建等效的小写构造函数:

代码语言:javascript
复制
number :: Int -> Expression
number n = Number n
-- Or:
-- number = Number

addition :: Expression -> Expression -> Expression
addition a b = Addition a b
-- Or:
-- addition = Addition

…

第二种技术被称为无标记的最终风格。它稍微高级一些,可能不是您在这里想要的,但为了完整起见,我将它包括在内。本质上,您可以创建一个类型类型,表示作为表达式解释的类型集,而不是数据类型。在您的示例中,它们是String,表示将表达式解释为漂亮打印的文本(writeOut),以及Int (或其他数字类型,如IntegerRationalDouble),即将表达式解释为值。

您可以通过创建一个类型类型来表达这一点,其方法是表达式中可能的术语:

代码语言:javascript
复制
-- The class of interpretations of expressions.
class Interpretation a where
  number :: Int -> a
  addition :: a -> a -> a
  subtraction :: a -> a -> a
  multiplication :: a -> a -> a
  …

然后是不同解释类型的实例:

代码语言:javascript
复制
-- Place this at the top of the file if you include
-- the type signatures in the definitions below.
{-# LANGUAGE InstanceSigs #-}

instance Interpretation String where

  number :: Int -> String
  number n = show n
  
  addition :: String -> String -> String
  addition a b = concat ["(", a, " + ", b, ")"]

  …

instance Interpretation Int where

  number :: Int -> Int
  number n = n

  addition :: Int -> Int -> Int
  addition a b = a + b

  …

然后,像multiplication (addition …) (number 3)这样的表达式有一个重载类型Interpretation a => a,您可以在不同的具体类型中计算它:

代码语言:javascript
复制
> example :: Interpretation a => a; example = multiplication (addition (number 1) (subtraction (number 2) (number 3))) (number 3)

> example :: String
"((1+(2-3))*3)"

> example :: Int
0

然而,虽然这是灵活的,但它也使引入新操作更加复杂,并且需要一些稍微高级的类型系统特性,以便在运行时从字符串解析表达式。

因此,我建议您坚持数据类型方法,这是Haskeller在默认情况下通常会达到的方法,除非他们有特定的理由不这样做。

如果您希望将表达式的构造和解释局限于这些特定的函数,则只能从模块导出additionmultiplication和C函数,并将数据类型保持为私有,那么将参数构造为writeOut & calc的唯一方法是调用这些函数:

代码语言:javascript
复制
module Expr
  ( number
  , addition
  , subtraction
  …

  , writeOut
  , calc
  ) where

-- Expression data type, *not* exported:

data Expression = …

-- Constructor functions, exported:

number :: Int -> Expression
number = Number

…

-- Computations on expressions, also exported:

writeOut :: Expression -> String
writeOut = …

calc :: Expression -> Int
calc = …
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65432031

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档