我怎样才能用我的玩具语言正确评估这个值

How can I correctly evaluate this value in my toy language

我正在制作一种玩具语言,我希望能够使用相同的构造函数对 Floats 和 Integers 进行算术运算,就像这样(下面提供了最小示例)。

data Expr a where
  NumberConst :: Number a -> Expr (Number a)
  Mul         :: Expr (Number n) -> Expr (Number n) -> Expr (Number n)

data family Number :: * -> *
data instance Number Integer = NumInt Integer deriving (Eq, Ord, Show)
data instance Number Float   = NumFloat Float deriving (Eq, Ord, Show)

class (Eq a, Ord a, Num a) => SquanchyNum a where

  toDigit  :: Number a -> a
  divide :: Number a -> Number a -> a
  mul    :: Number a -> Number a -> a
  sub    :: Number a -> Number a -> a
  add    :: Number a -> Number a -> a

instance SquanchyNum Integer where

  toDigit  (NumInt x)            = x
  divide (NumInt x) (NumInt y) = x `div` y
  mul    (NumInt x) (NumInt y) = x * y
  sub    (NumInt x) (NumInt y) = x - y
  add    (NumInt x) (NumInt y) = x + y

instance SquanchyNum Float where

  toDigit  (NumFloat x)                = x
  divide (NumFloat x) (NumFloat y) = x / y
  mul    (NumFloat x) (NumFloat y) = x * y
  sub    (NumFloat x) (NumFloat y) = x - y
  add    (NumFloat x) (NumFloat y) = x + y


eval :: Expr a -> a
eval (NumberConst a) = a
eval (Mul a b) = (eval a) `mul` (eval b) <-- this line makes the problem visible

在 ghci 中我得到以下错误

*Main> :r
[2 of 3] Compiling Lib              ( /home/michael/git/brokensquanchy/src/Lib.hs, 
 interpreted )

/home/michael/git/brokensquanchy/src/Lib.hs:45:18: error:
   • Occurs check: cannot construct the infinite type: n ~ Number n
     Expected type: a
     Actual type: n
• In the expression: (eval a) `mul` (eval b)
  In an equation for ‘eval’: eval (Mul a b) = (eval a) `mul` (eval b)
• Relevant bindings include
    b :: Expr (Number n)
      (bound at /home/michael/git/brokensquanchy/src/Lib.hs:45:13)
    a :: Expr (Number n)
      (bound at /home/michael/git/brokensquanchy/src/Lib.hs:45:11)
|
45 | eval (Mul a b) = (eval a) `mul` (eval b)
   |                  ^^^^^^^^^^^^^^^^^^^^^^^
Failed, one module loaded.

但是 当我在 ghci 中手动尝试以下操作时它有效

(eval (NumberConst (NumInt 6))) `mul` (eval (NumberConst (NumInt 2)))
12

这让我相信我没有在 eval 的类型签名中提供足够的细节。解决办法是什么?

编辑 - 我认为我没有正确解决这个问题或没有清楚地解释它。

解决 luqui 的问题

You are already marking the type with the a -- you can deduce from the type which constructor it will be -- the constructor does nothing for you. Why not banish Number a from this source file, just replacing it with a everywhere?

好的,让我们用 NumberConst

来做
data Expr a where
  NumberConst :: a -> Expr a

现在我可以做到了

:t NumberConst ("bad code") 
NumberConst ("bad code") :: Expr [Char]

我不想那样做。

所以,我需要一种方法来 (1) 强制我的算术构造函数只能有一个 Int 或一个 Float (2) 避免重复的构造函数,一个用于 Int 和 Float。我应该能够在 IntFloat 上使用算术构造函数。 (3) 能够知道它是 Int 还是 Float 以便我可以处理除法。

数据族加类型 class 是我认为能够实现的方式。如果没有必要,我完全支持更简单的方法。我只是不知道那是什么。

Okay, let's do that with NumberConst

data Expr a where<br> NumberConst :: a -> Expr a
Now I can do this
:t NumberConst ("bad code")
NumberConst ("bad code") :: Expr [Char]
I don't want to be able to do that.

我明白了。这里我们 运行 有点哲学问题:强类型的意义是什么?许多程序员会说,拒绝格式错误的程序。这当然是一个优势,但是 Haskell 长期以来一直遵循 Simon Peyton Jones 所说的 sexy types 的方向:类型签名不应该只是 forbid 东西,而是引导你让程序自动正确。

换句话说,为了防止NumberConst "bad code"而限制NumberConst中的类型并不性感。相反,您应该只需要实际需要的 属性 。这不需要任何数据族,只需要一个类型类。看起来你基本上只需要 Num / Fractional,除了一个除法,整数类型的 div 和浮点类型的 / 加倍。 (顺便说一句,这可能不是个好主意,但让我们继续吧。)

class Num a => Divisible a where
  divide :: a -> a -> a
instance Divisible Int where divide = div
instance Divisible Float where divide = (/)

data Expr a where
  NumberConst :: Divisible a => a -> Expr a
  Mul         :: Expr n -> Expr n -> Expr n

这个约束足以证明每一个非无限Expr a都满足Divisible a。当您实施 eval 时,您会发现 GHC 拒绝承认这一点。作为解决方案,您可以简单地将约束也添加到 Mul

  Mul         :: Divisible n => Expr n -> Expr n -> Expr n

...严格来说这是多余的,但这可能无关紧要;或者你可以实现一个延续传递风格的辅助函数,从表达式中提取证明:

{-# LANGUAGE RankNTypes #-}

evalCPS :: Expr a -> (Divisible a => a -> r) -> r
evalCPS (NumberConst n) q = q n
evalCPS (Mul x y) q = x `evalCPS` \x' -> q $ x' * eval y

eval :: Expr a -> a
eval = evalCPS id

(3) be able to know if it's an Int or a Float so that I can handle division.

上述解决方案无法做到这一点。事实上,以后任何人都可以添加更多实例。如果你不想那样,如果你真的只想允许 IntFloat 并希望能够找出它是哪一个,那么我建议你 do 使用单独的构造函数,但仅用于常量:

data Expr a where
  IntConst :: Int -> Expr Int
  FloatConst :: Float -> Expr Float
  Mul :: Expr a -> Expr a -> Expr a

现在,您可以再次使用 eval 的 CPS 版本,它允许根据它是 Int 还是 Float:

进行不同的延续
evalCPSMono :: Expr a -> (Int -> r) -> (Float -> r) -> r
evalCPSMono (IntConst i) qI _ = qI i
evalCPSMono (FloatConst w) _ qF = qF w
evalCPSMono (Mul x y) qI qF
   = evalCPSMono x (\x' -> qI $ x' * eval y)
                   (\x' -> qF $ x' * eval y)

或者,您可以使用 封闭类型族 .

表达对两种类型的限制
type NumberAllower a where
  NumberAllower Int = Int
  NumberAllower Float = Float
  NumberAllower a = Void

data Expr a where
  NumberConst :: NumberAllower a -> Expr a
  ...

这可以防止任何人写 NumberConst "bad code"。但它 而不是 的作用是让您找出 Expr 中包含的特定类型。无论如何,您最终不得不从外部手动要求它。不性感。

另一个仍然只有一个 NumberConst 构造函数,但确实让您获得确切类型的替代方案是(这可能是您首先尝试编写的内容):

data Number a where
  It'sInt :: Int -> Number Int
  It'sFloat :: Float -> Number Float

data Expr a where
  NumberConst :: Number a -> Expr a
  Mul :: Expr a -> Expr a -> Expr a

这是在实践中