用户定义数据类型的操作 [英] Operations with user defined Datatype

查看:106
本文介绍了用户定义数据类型的操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个数据类型

  data Expr = ExprNum Double  - 常量
| ExprVar字符串 - 变量
| ExprAdd Expr Expr
| ExprSub Expr Expr
| ExprNeg Expr - 一元' - '操作符
| ExprMul Expr Expr
| ExprDiv Expr Expr
导出显示

如果我有(3 * 4 + 5)数据类型 Expr
它应该返回 17 ,if表达式包含一个变量:(3 * x)它应该返回 ExprMul(ExprNum 3)(ExprVarx)



我试过这样做:

  calculate(ExprMul ab)= a * b 
计算(ExprAdd ab)= a + b
计算(ExprDiv ab)= a`div` b
计算(ExprSub ab)= a - b
计算(ExprVar a)= a
计算(ExprNum a)=读取a:Double

但我无法执行它。
我在这里做错了什么?



我想知道的另一件事是,如果用户输入Expr类型的值,并且需要将其转换为字符串格式,即如果用户输入为

$ $ $ $ $ $ ExprAdd(ExprNum 1)(ExprVarx)

我希望得到如下输出:

<$ p $

1 + x

我试过使用 Read



请任何人都可以帮忙。谢谢。

解决方案

看看你的问题和你的例子,我认为我们可以用一些很好的方式来使用一些类型来获得一些语法糖。



你可以在最后找到完整的代码,但是由于Luke对我的一个很好的解释,我没有解释这些东西,所以我们就开始做这件事。



首先我们必须在执行 calculate 时小心处理一些小问题,然后我们可以开始查找g为一些语法糖来支持输入,如(3 * 4 + 5):: Expr 执行 calculate

这对我来说并不是100%清楚,但我认为您希望您的 calculate 来签名:

  calculate :: Expr  - > Expr 

大多数情况下,您会看到诸如 evaluate :: Expr - >也许Double 但我认为在这种情况下(因为你想要 3 * x ExprMul(ExprNum 3)(ExprVar x))我们只是想简化使用 calculate



所以让我们试着简化一下。



对于形式为 ExprNum ExprVar 我们可以不做任何事情来简化事情,所以让我们诚实地说出来:

 计算一个@(ExprNum _)= a 
计算一个@(ExprVar _)= a

如果你没有看到 a @(ExprNum _)语法,它只是匹配(ExprNum _)但要记住 a 中的compelte匹配表达式。



考虑到这一点,我们必须关心更多有趣的情况 - 例如乘法:

  calculate(ExprMul ab)= a * b 

问题在于您的右侧 a * b 现在,这不能工作,因为(*)需要它的操作数在 Num Expr 尚未(至今)。



当然,我们真正想要的是如果可以的话计算产品 - 意味着如果 a b 确实是数字 - 或者在这种情况下是 ExprNum



最简单的方法来检查这个(我可以想到)是递归简化两个操作符 a b ,然后使用 case 表达式:

  calculate(ExprMul ab)= let a'=计算a 
b'=计算b
',b')
(ExprNum a'',ExprNum b'') - > ExprNum(a''* b'')
_ - >我们来看看这一步:



  • 首先将 a b 简化为 a' b'使用计算 recursivley

  • 然后,如果这两个都是数字( ExprNum ),那么它会将它们的值的乘积作为另一个 ExprNum
  • 如果它们不是 ExprNum 它只是产生 ExprMul 使用简化的术语


    最后一点是魔法发生的地方 - 因为我们得到了算法将尝试的简化术语简化子类,即使变量在附近,我们也无法得到完整的评估。



    重构一下



    现在我们可以对 ExprDiv ExprAdd 等其他表达式执行相同的操作,但我并不喜欢重复一遍又一遍的东西,让我们重构一下:

      operateOnNums ::(Expr  - > Expr  - > Expr)
    - > (Double - > Double - > Double)
    - > Expr - > Expr - > (a',b')为
    (ExprNum a'',ExprNum b)的情况下计算b $ b b'=计算b
    defpr = b $ b operationOnNums def fab = '') - > ExprNum(f a''b'')
    _ - > def a'b'

    operateOnNum ::(Expr - > Expr)
    - > (Double - > Double)
    - > Expr - > Expr
    operateOnNum def f a = let a'=在
    的前提下计算
    ExprNum a'' - > ExprNum(f a'')
    _ - > def a'

    即使这些看起来有点儿差不多,我们也是如此更复杂。
    我们再次检查是否可以简化子项(两个用于 operateOnNums ,另一个用于 operateOnNum ExprNum 表达式,如果是这样,则将这些值的
    值应用于函数 f (这将是操作在实数上 - 例如(*)用于 ExprMul 否定 def (意思是* defaults *)将简化的子程序包装到 ExprNeg中,现在计算自己看起来相当不错(或者我认为):

      

    code> calculate :: Expr - > Expr
    计算(ExprMul ab)= operateOnNums ExprMul(*)ab
    计算(ExprAdd ab)= operateOnNums ExprAdd(+)ab
    计算(ExprDiv ab)= operateOnNums ExprDiv(/)ab
    计算(ExprSub ab)= operateOnNums ExprSub( - )ab
    计算(ExprNeg a)= operateOnNum ExprNeg否定a
    计算a = a

    ,我想我们可以继续。



    启用语法糖:实现一些类型类

    h2>

    Num



    这给我们留下了一个问题,希望能够输入 3 * 4 + 5 :: Expr 之类的东西。



    现在有一个基本的类型类名为 Num ,它为我们提供了完全相同的方法。
    你基本上必须告诉Haskell如何做一个足够大的子集,像 * + ,..以及一个函数 fromIntegral ,它将翻译数字,如 0 1 2 ,... into Expr



    真正好的是,我们有那些运算符,甚至来自整数已经以我们的类型构造函数 em> ExprMul ExprAdd ,..和 ExprNum

    因此,让我们使 Expr 一个 Num 的实例:

     实例Num Expr其中
    a + b = ExprAdd ab
    a * b = ExprMul ab
    否定a = ExprNeg a
    fromInteger n = ExprNum(fromInteger n)
    abs _ = undefined
    signum _ = undefined

    容易不是吗?

    注意我欺骗了使用 abs signum 函数。如果你喜欢,如果你为它们添加其他 Expr 个案并相应补充 calculate 函数,你也可以实现这些 - 但对于OP给出的例子,他们并不是真的需要在这里。



    这样做已经可以工作了:

     >计算$ 3 * 4 + 5 
    ExprNum 17.0

    >计算$ 3 * 4 + ExprVara
    ExprAdd(ExprNum 12.0)(ExprVara)



    IsString



    @behklir建议实现这个类型 - 使用this和 OverloadedString 扩展,我们将能够评估如下所示:

     >计算$ 3 * 4 +a
    ExprAdd(ExprNum 12.0)(ExprVara)



    使用 fromString 从<$转换a,OverloadedStrings
    c $ c> IsString
    ,而不是将它编译成 [char] ,所以让我们实现 IsString - 这又很简单,因为我们已经有 fromString 形式为 ExprVar

      instance IsString Expr where 
    fromString = ExprVar

    不要忘记用 { - #LANGUAGE OverloadedStrings# - } OverloadedStrings 来源。
    在GHCi中,您可能想要
    - 以开始它> ghci -XOverloadedStrings
    - :set -XOverloadedStrings GHCi内
    $ b

    小数



    这个是@ØrjanJohansen的一个建议,它可以让我们编写像



    的东西,我认为你可以像这样工作:

     >计算$a/ 4.5 
    ExprDiv(ExprVara)(ExprNum 4.5)

    这很像是小数值和分数的 Num - 我想你知道现在发生了什么:

      instance Fractional Expr其中
    fromRational r = ExprNum(fromRational r)
    a / b = ExprDiv ab

    就是这样 - 请确保指向我的东西不是100%清晰或拼写错误。



    如何评估变量



    您可能会问您如何评估变量。



    由于我们不再对结果表达式感兴趣,在它的值中,我们称之为 evaluate

      import Data.Maybe fromMaybe)

    evaluate :: [(String,Double)] - > Expr - > Double
    评估env(ExprMul ab)=评估env a *评估env b
    评估env(ExprAdd ab)=评估env a +评估env b
    评估env(ExprDiv ab)=评估env a评估env b
    评估env(ExprSub ab)=评估env a - 评估env b
    评估env(ExprNeg a)=否定评估env a
    评估_(ExprNum n)= n
    评估env(ExprVar v)= fromMaybe 0 $ lookup v env

    应该是直截了当的 - 唯一真正的新事物是 env
    我们需要知道变量的值。所以我们传入一个包含( variable value )对的环境。然后,我们可以使用 lookup 以及 fromMaybe 来查找变量的值。



    一个例子可能如下所示:

     >评估[(a,5)](3 * 4 +a)
    17.0

    正如您所看到的,我只提供了一对,匹配a 5 - 然后使用使用 ExprVara(这里当然是隐藏使用 IsString OverloadedStrings )。



    如果算法找不到匹配变量 lookup 将会返回 Nothing ,这就是 fromMaybe 的作用:我决定将变量默认为 0 在这种情况下,这正是 fromMaybe 0 :: Maybe Double - > Double 确实(在这里)。



    如果你不想要默认的0行为



    如果您不喜欢它,那么对于不在环境中的变量,这将返回 0 ,您可以更改 evaluate <


















    $ b $ import Control.Applicative((< $>))
    import Control.Monad(liftM2)

    evaluate :: [(String,Double)] - > ; Expr - >也许Double
    评估env(ExprMul ab)= liftM2(*)(评估env a)(评估env b)
    评估env(ExprAdd ab)= liftM2(+)(评估env a)(评估env b)
    评估env(ExprDiv ab)= liftM2(/)(评估env a)(评估env b)
    评估env(ExprSub ab)= liftM2( - )(评估env a)(评估env b)
    评估env(ExprNeg a)=否定< $>评估env a
    evaluate _(ExprNum n)=只是n
    评估env(ExprVar v)= lookup v env

    这当然会使用一些沉重的武器( liftM2 brining (*))进入也许 monad和(< $>)取反相同

    请理解我无法再写出另一大块文字来详细解释。



    基本上那些只是因为我懒得对评估env a 评估env b 处理4个案例( Nothing,Nothing Nothing,Just ,...) - I我只关心 Just,Just 无论如何,这些都是这样做的:在 Just case中执行操作并返回 Nothing 其他地方。



    完整代码



    参考和更容易复制和粘贴这里是完整的代码:



    $ $ b $












    $ import $ Data.Maybe(fromMaybe )
    import Data.String(IsString(..))

    data Expr = ExprNum Double - 常量
    | ExprVar字符串 - 变量
    | ExprAdd Expr Expr
    | ExprSub Expr Expr
    | ExprNeg Expr - 一元' - '操作符
    | ExprMul Expr Expr
    | ExprDiv Expr Expr
    派生显示

    实例Num Expr其中
    a + b = ExprAdd ab
    a * b = ExprMul ab
    否定a = ExprNeg a
    fromInteger n = ExprNum(fromInteger n)
    abs _ = undefined
    signum _ = undefined

    实例小数Expr其中
    fromRational r = ExprNum(fromRational r)
    a / b = ExprDiv ab

    实例IsString Expr其中
    fromString = ExprVar

    评估:: [(String,Double)] - > Expr - > Double
    评估env(ExprMul ab)=评估env a *评估env b
    评估env(ExprAdd ab)=评估env a +评估env b
    评估env(ExprDiv ab)=评估env a评估env b
    评估env(ExprSub ab)=评估env a - 评估env b
    评估env(ExprNeg a)=否定评估env a
    评估_(ExprNum n)= n
    评估env(ExprVar v)= fromMaybe 0 $ lookup v env

    calculate :: Expr - > Expr
    计算(ExprMul ab)= operateOnNums ExprMul(*)ab
    计算(ExprAdd ab)= operateOnNums ExprAdd(+)ab
    计算(ExprDiv ab)= operateOnNums ExprDiv(/)ab
    计算(ExprSub ab)= operateOnNums ExprSub( - )ab
    计算(ExprNeg a)= operateOnNum ExprNeg否定a
    计算a = a


    operateOnNums ::(Expr - > Expr - > Expr) - > (双 - >双 - >双) - > Expr - > Expr - > (a',b')为
    (ExprNum a'',ExprNum b)的情况下计算b $ b b'=计算b
    defpr = b $ b operationOnNums def fab = '') - > ExprNum(f a''b'')
    _ - > def a'b'

    operateOnNum ::(Expr - > Expr) - > (Double - > Double) - > Expr - > Expr
    operateOnNum def f a = let a'=在
    的前提下计算
    ExprNum a'' - > ExprNum(f a'')
    _ - > def a'



    一些例子



     >计算$ 3 * 4 + 5 
    ExprNum 17.0

    >计算$ 3 * 4 +a
    ExprAdd(ExprNum 12.0)(ExprVara)

    >计算$ 3 *a+5
    ExprAdd(ExprMul(ExprNum 3.0)(ExprVara))(ExprNum 5.0)

    > (ExprMum(ExprNum 3.0)(ExprNum 4.0))(ExprVara)

    >计算$a/ 4.5
    ExprDiv(ExprVara)(ExprNum 4.5)

    >评估[(a,5)](3 * 4 +a)
    17.0

    这是(我认为)你想从什么开始



    备注:



    不要忘记在GHCi中启用 OverloadedStrings ,如果你想试试这个:
    - 用开始它> ghci -XOverloadedStrings
    - :set -XOverloadedStrings 在GHCi中


    I have a datatype

    data Expr = ExprNum Double -- constants
              | ExprVar String -- variables
              | ExprAdd Expr Expr
              | ExprSub Expr Expr
              | ExprNeg Expr -- The unary '-' operator
              | ExprMul Expr Expr
              | ExprDiv Expr Expr
              deriving Show
    

    If I have (3* 4 + 5) of datatype Expr it should return 17 and if the expression includes a variable: (3 * x) it should return ExprMul (ExprNum 3) (ExprVar "x")

    I tried doing it this way:

    calculate (ExprMul a b) = a * b
    calculate (ExprAdd a b) = a + b
    calculate (ExprDiv a b) = a `div` b
    calculate (ExprSub a b) = a - b
    calculate (ExprVar a )= a
    calculate (ExprNum a ) = Read a : Double
    

    But I am unable to execute it. What am I doing wrong here?

    Another thing that I wish to know about is that if a user enters a value in Expr type ,and I need to convert it into string format,i.e,if User enters as

    ExprAdd (ExprNum 1) (ExprVar "x")
    

    I wish to get the output as follows

    1+x
    

    I tried using Read but I am unable to execute it.

    Please if anyone can help.Thankyou.

    解决方案

    Looking at your question and your examples, I think we can make this happen in a nice way using some type-classes to get some syntactic sugar.

    You can find the complete code at the end but as Luke made a good point on me not explaining stuff let's start doing this.

    First we have to take care on some minor problems in your implementation of calculate and then we can start looking for some syntactic sugar to enable input like (3* 4 + 5) :: Expr.

    implementing calculate

    It was not 100% clear to me but I think you want your calculate to have this signature:

    calculate :: Expr -> Expr
    

    I most cases you see things like evaluate :: Expr -> Maybe Double but I think in this case (because you want 3 * x to be ExprMul (ExprNum 3) (ExprVar "x")) we want indeed just to simplify terms using calculate.

    So let's try to simplify things.

    For expressions of the form ExprNum and ExprVar we can do nothing more to simplify things as it is, so let's be honest and say so:

    calculate a@(ExprNum _) = a
    calculate a@(ExprVar _) = a
    

    If you did not see the a@(ExprNum _) syntax before it's just fancy way of matching (ExprNum _) but remembering the compllete matched expression in a.

    With this in mind we have to take care of the more interesting cases - for example multiplication:

    calculate (ExprMul a b) = a * b
    

    The problem is your right side a * b Right now this cannot work as (*) needs to have it's operands to be in Num and Expr is not (yet).

    Of course what we really want is to calculate the product if we can - meaning if both a and b are indeed numbers - or in this case ExprNum.

    The easiest way to check this (I can come up with) is to recursivley simplify both operants a and b and then to use a case expression:

    calculate (ExprMul a b) = let a' = calculate a
                                  b' = calculate b
                              in case (a',b') of
                                 (ExprNum a'', ExprNum b'') -> ExprNum (a''*b'')
                                 _                          -> ExprMul a' b'
    

    Let's look at this step by step:

    • first it simplifies a and b into a' and b' using calculate recursivley
    • then if both of these are numbers (ExprNum) it yield the product of their values as another ExprNum
    • if they are not both ExprNums it just yield an ExprMul using the simplified term

    The last point is where a bit of the magic happens - because we yield the simplified terms the algorithm will try to simplify subterms even if a variable is around and we cannot get a complete evaluation.

    refactoring a bit

    Now we can do the same to the other expressions like ExprDiv, ExprAdd, ... but I did not really like to repeat the stuff again and again so let's refactor this out:

    operateOnNums :: (Expr -> Expr -> Expr) 
                  -> (Double -> Double -> Double) 
                  -> Expr -> Expr -> Expr
    operateOnNums def f a b = let a' = calculate a
                                  b' = calculate b
                              in case (a',b') of
                                (ExprNum a'', ExprNum b'') -> ExprNum (f a'' b'')
                                _                          -> def a' b'
    
    operateOnNum :: (Expr -> Expr) 
                 ->  (Double -> Double) 
                 -> Expr -> Expr
    operateOnNum def f a = let a' = calculate a
                           in case a' of
                             ExprNum a'' -> ExprNum (f a'')
                             _           -> def a'
    

    This is really just the same we did above even if these might seem to be a bit more complicated. Again we only check if we can simplify subterms (two for operateOnNums and only one for operateOnNum) into ExprNum expressions and if so apply the value of those to a function f (that will be the operations on the real numbers - like (*) for ExprMul or negate for ExprNeg) and if not usedef(meaning *defaults*) to wrap the simplified subterms intoExpr`s again.

    Calculate itself now look rather nice (or so I think):

    calculate :: Expr -> Expr
    calculate (ExprMul a b) = operateOnNums ExprMul (*) a b
    calculate (ExprAdd a b) = operateOnNums ExprAdd (+) a b
    calculate (ExprDiv a b) = operateOnNums ExprDiv (/) a b
    calculate (ExprSub a b) = operateOnNums ExprSub (-) a b
    calculate (ExprNeg a)   = operateOnNum ExprNeg negate a
    calculate a             = a
    

    and I think we can move on.

    enabling syntactic sugar: implementing some type-classes

    Num

    This leaves us with the problem that we want to be able to input something like 3*4+5 :: Expr.

    Now there is a basic type-class named Num that provides us exactly with the means to do so. You basically have to tell Haskell how to do a big enough subset of the basic math-operators like *, +, .. together with a function fromIntegral that will translate numbers like 0, 1, 2, ... into Expr.

    What's really nice here is, that we have those operators and even the fromIntegral already handy in form of our type-constructors ExprMul, ExprAdd, .. and ExprNum.

    So let's make Expr an instance of Num:

    instance Num Expr where
      a + b         = ExprAdd a b
      a * b         = ExprMul a b
      negate a      = ExprNeg a
      fromInteger n = ExprNum (fromInteger n)
      abs _         = undefined
      signum _      = undefined
    

    Easy isn't it?

    Note that I cheated with the abs and signum functions. If you like you can implement these too if you add other Expr cases for them and complement the calculate function accordingly - but for the examples the OP gave they are not really needed here.

    With this done this will already work:

    > calculate $ 3*4+5
    ExprNum 17.0
    
    > calculate $ 3*4 + ExprVar "a"
    ExprAdd (ExprNum 12.0) (ExprVar "a")
    

    IsString

    @behklir suggested to implement this type-class as well - using this and the OverloadedString extension we will be able to evaluate something like this:

    > calculate $ 3*4+"a"
    ExprAdd (ExprNum 12.0) (ExprVar "a")
    

    OverloadedStrings is there to translate "a" using the fromString from IsString instead of just compiling it into a [char], so let's implement IsString - which again is really easy as we already have fromString in the form of ExprVar:

    instance IsString Expr where
      fromString = ExprVar
    

    Don't forget to enable OverloadedStrings with {-# LANGUAGE OverloadedStrings #-} in source. In GHCi you might want to - start it with ghci -XOverloadedStrings - :set -XOverloadedStrings inside GHCi

    Fractional

    This one was a suggestion from @ØrjanJohansen to enable us to write things like

    I think you can get this working something like this:

    > calculate $ "a" / 4.5
    ExprDiv (ExprVar "a") (ExprNum 4.5)
    

    It's much like Num for fractional values and the division - I think you know what comes by now:

    instance Fractional Expr where
      fromRational r = ExprNum (fromRational r)
      a / b          = ExprDiv a b
    

    That's it - please make sure to point me to things not 100% clear or misspelled.

    How to evaluate Variables

    You might ask how you could evaluate Variables.

    As we are no longer interested in a resulting expression but only in it's value let's call it evaluate:

    import Data.Maybe (fromMaybe)
    
    evaluate :: [(String, Double)] -> Expr -> Double
    evaluate env (ExprMul a b) = evaluate env a * evaluate env b
    evaluate env (ExprAdd a b) = evaluate env a + evaluate env b
    evaluate env (ExprDiv a b) = evaluate env a / evaluate env b
    evaluate env (ExprSub a b) = evaluate env a - evaluate env b
    evaluate env (ExprNeg a)   = negate $ evaluate env a
    evaluate _   (ExprNum n)   = n
    evaluate env (ExprVar v)   = fromMaybe 0 $ lookup v env
    

    Most of this should be straight forward - the only really new thing is env: We need to know what value a variable has. So we pass in an environment with (variable,value) pairs. We can then use lookup together with fromMaybe to find values for variables.

    An example could look like this:

    > evaluate [("a",5)] (3*4+"a")
    17.0
    

    As you can see I just provided a single pair, matching "a" to 5 - and then use an expression using ExprVar "a" (here of course hidden using IsString and OverloadedStrings).

    In case the algorithm will not find a matching variable lookup will return Nothing and this is where fromMaybe comes into play: I decided to default variables to 0 in this case and this is exactly what fromMaybe 0 :: Maybe Double -> Double does (here).

    if you don't want no default-to-0 behaviour

    In case you don't like it that this will return 0 for variables not in the environment you can change evaluate to be partial (or better: return Maybe Double) like this:

    import Control.Applicative((<$>))
    import Control.Monad (liftM2)
    
    evaluate :: [(String, Double)] -> Expr -> Maybe Double
    evaluate env (ExprMul a b) = liftM2 (*) (evaluate env a) (evaluate env b)
    evaluate env (ExprAdd a b) = liftM2 (+) (evaluate env a) (evaluate env b)
    evaluate env (ExprDiv a b) = liftM2 (/) (evaluate env a) (evaluate env b)
    evaluate env (ExprSub a b) = liftM2 (-) (evaluate env a) (evaluate env b)
    evaluate env (ExprNeg a)   = negate <$> evaluate env a
    evaluate _   (ExprNum n)   = Just n
    evaluate env (ExprVar v)   = lookup v env
    

    This of course uses some heavy weaponary (liftM2 brining (*) into the Maybe monad and (<$>) doing the same with negate).

    Please understand that I cannot write another big block of text to explain those in detail.

    Basically those are just there because I am to lazy to pattern-match on the results of evaluate env a and evaluate env b to do handle 4 cases (Nothing, Nothing, Nothing, Just, ...) - I am only interested in Just,Just anyway and those do exactly this: do the operation in the Just cases and return Nothing everywhere else.

    The complete code

    For reference and easier copy&paste here is the complete code:

    {-# LANGUAGE OverloadedStrings #-}
    
    module Expressions where
    
    import Data.Maybe (fromMaybe)
    import Data.String (IsString(..))
    
    data Expr = ExprNum Double -- constants
              | ExprVar String -- variables
              | ExprAdd Expr Expr
              | ExprSub Expr Expr
              | ExprNeg Expr -- The unary '-' operator
              | ExprMul Expr Expr
              | ExprDiv Expr Expr
              deriving Show
    
    instance Num Expr where
      a + b          = ExprAdd a b
      a * b          = ExprMul a b
      negate a       = ExprNeg a
      fromInteger n  = ExprNum (fromInteger n)
      abs _          = undefined
      signum _       = undefined
    
    instance Fractional Expr where
      fromRational r = ExprNum (fromRational r)
      a / b          = ExprDiv a b
    
    instance IsString Expr where
      fromString     = ExprVar
    
    evaluate :: [(String, Double)] -> Expr -> Double
    evaluate env (ExprMul a b) = evaluate env a * evaluate env b
    evaluate env (ExprAdd a b) = evaluate env a + evaluate env b
    evaluate env (ExprDiv a b) = evaluate env a / evaluate env b
    evaluate env (ExprSub a b) = evaluate env a - evaluate env b
    evaluate env (ExprNeg a)   = negate $ evaluate env a
    evaluate _   (ExprNum n)   = n
    evaluate env (ExprVar v)   = fromMaybe 0 $ lookup v env
    
    calculate :: Expr -> Expr
    calculate (ExprMul a b) = operateOnNums ExprMul (*) a b
    calculate (ExprAdd a b) = operateOnNums ExprAdd (+) a b
    calculate (ExprDiv a b) = operateOnNums ExprDiv (/) a b
    calculate (ExprSub a b) = operateOnNums ExprSub (-) a b
    calculate (ExprNeg a)   = operateOnNum ExprNeg negate a
    calculate a             = a
    
    
    operateOnNums :: (Expr -> Expr -> Expr) ->  (Double -> Double -> Double) -> Expr -> Expr -> Expr
    operateOnNums def f a b = let a' = calculate a
                                  b' = calculate b
                              in case (a',b') of
                                (ExprNum a'', ExprNum b'') -> ExprNum (f a'' b'')
                                _                          -> def a' b'
    
    operateOnNum :: (Expr -> Expr) ->  (Double -> Double) -> Expr -> Expr
    operateOnNum def f a = let a' = calculate a
                           in case a' of
                             ExprNum a'' -> ExprNum (f a'')
                             _           -> def a'
    

    some examples

    > calculate $ 3*4+5
    ExprNum 17.0
    
    > calculate $ 3*4+"a"
    ExprAdd (ExprNum 12.0) (ExprVar "a")
    
    > calculate $ 3*"a"+5
    ExprAdd (ExprMul (ExprNum 3.0) (ExprVar "a")) (ExprNum 5.0)
    
    > calculate $ 3*4+"a"
    ExprAdd (ExprMul (ExprNum 3.0) (ExprNum 4.0)) (ExprVar "a")
    
    > calculate $ "a" / 4.5
    ExprDiv (ExprVar "a") (ExprNum 4.5)
    
    > evaluate [("a",5)] (3*4+"a")
    17.0
    

    which is (I think) what you wanted to start with

    remarks:

    Don't forget to enable OverloadedStrings in GHCi if you want to try this: - start it with ghci -XOverloadedStrings - :set -XOverloadedStrings inside GHCi

    这篇关于用户定义数据类型的操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆