我正在尝试执行一些操作,这些操作将作为在“where”子句之后定义的输入函数,并输出一个由它组成的字符串。这里提到的是将“where”子句后面列出的函数范围扩展到writeOut函数的内容。我想要的东西(不起作用):
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++")"
...因此,例如,对于输入:
writeOut multiplication (addition (number '1') (subtraction (number '2') (number '3'))) (number '3')会给我一个输出:
(1+(2-3))*3当我删除writeOut函数并保留“where”关键字之后的内容时,它正常工作,但是我真的想在'writeOut‘函数下“锁定它”。
您可能会问我为什么要这样做,因为我想要另一个函数来为我计算表达式:
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以防万一你想看看什么是“正常”的:
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++")"发布于 2020-12-23 23:31:04
您似乎正在尝试为该DSL实现DSL和评估器。而不是一组函数,定义一个数据类型,然后为该类型编写一个评估器。
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,而不是显示它:
evaluate :: Expr -> Int
evaluate = undefined发布于 2020-12-24 00:06:06
目前,您对number、multiplication、addition等的定义都是在字符串或字符上操作的函数。因此,即使假设它们是在顶层定义的,所以您也可以在这样的表达式中引用它们:
writeOut multiplication (addition (number '1') (subtraction (number '2') (number '3'))) (number '3')这意味着writeOut必须能够接受函数multiplication作为它的第一个参数,(addition …)的结果作为它的第二个,(number …)的结果作为它的第三个,所以它需要一个类似([Char] -> [Char] -> [Char]) -> [Char] -> [Char] -> [Char]的类型。以这种方式重载writeOut并允许任何其他表达式作为参数是不可能的;如果您尝试使用类型类型来实现它,您将很快发现它会产生许多不明确的类型,因此需要大量类型注释。
但是有许多更好的方法,取决于你在这里真正想要完成的是什么。最简单的方法是将这些函数移到顶层:
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函数计算表达式的值,则需要在某个地方对此进行概括。
有两种典型的方法可以做到这一点。
一种方法是创建用于表示表达式的数据类型,然后您的两个函数writeOut和calc将使用该类型的值,并分别生成一个字符串或数字:
-- 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中:
> :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注意,您仍然需要writeOut或calc参数周围的括号;如果您编写了writeOut Plus (Number 1) (Number 2),这将意味着用三个参数调用writeOut,即Plus、Number 1和Number 2,这不是您想要的。
如果不想使用大写构造函数名,则可以创建等效的小写构造函数:
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 (或其他数字类型,如Integer、Rational或Double),即将表达式解释为值。
您可以通过创建一个类型类型来表达这一点,其方法是表达式中可能的术语:
-- 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
…然后是不同解释类型的实例:
-- 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,您可以在不同的具体类型中计算它:
> 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在默认情况下通常会达到的方法,除非他们有特定的理由不这样做。
如果您希望将表达式的构造和解释局限于这些特定的函数,则只能从模块导出addition、multiplication和C函数,并将数据类型保持为私有,那么将参数构造为writeOut & calc的唯一方法是调用这些函数:
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 = …https://stackoverflow.com/questions/65432031
复制相似问题