【问题标题】:Is it possible to get `-=` working with literals?是否可以让 `-=` 使用文字?
【发布时间】:2014-05-26 17:54:20
【问题描述】:

今天找到this post on Quora,声称是

factorial(n) = def $ do    
    assert (n<=0) "Negative factorial"    
    ret <- var 1    
    i <- var n    
    while i $ do    
        ret *= i    
        i -= 1
    return ret

可能是正确的 Haskell 代码。我很好奇,ended up with

factorial :: Integer -> Integer
factorial n = def $ do
  assert (n >= 0) "Negative factorial"
  ret <- var 1
  i   <- var n
  while i $ do
      ret *= i
      i   -= 1
  return ret

使用var = newSTRefdefassertwhile 的规范定义,以及

a *= b = readSTRef b >>= \b -> modifySTRef a ((*) b)
a -= b = modifySTRef a ((+) (negate b))

但是,(*=)(-=) 有不同的类型:

(-=) :: Num a => STRef s a -> a -> ST s ()
(*=) :: Num a => STRef s a -> STRef s a -> ST s ()

所以ret -= i 不起作用。我试图为此创建一个合适的类型类:

class (Monad m) => NumMod l r m where
  (+=) :: l -> r -> m ()
  (-=) :: l -> r -> m ()
  (*=) :: l -> r -> m ()

instance Num a => NumMod (STRef s a) (STRef s a) (ST s) where
  a += b    = readSTRef b >>= \b -> modifySTRef a ((+) b)
  a -= b    = readSTRef b >>= \b -> modifySTRef a ((+) (negate b))
  a *= b    = readSTRef b >>= \b -> modifySTRef a ((*) b)

instance (Num a) => NumMod (STRef s a) a (ST s) where
  a += b    = modifySTRef a ((+) (b))
  a -= b    = modifySTRef a ((+) (negate b))
  a *= b    = modifySTRef a ((*) (b))

这确实有效,但前提是 factorial 返回 Integer。一旦我将返回类型更改为其他类型,它就会失败。我试图创建另一个实例

instance (Num a, Integral b) => NumMod (STRef s a) b (ST s) where
  a += b    = modifySTRef a ((+) (fromIntegral $ b))
  a -= b    = modifySTRef a ((+) (negate . fromIntegral $ b))
  a *= b    = modifySTRef a ((*) (fromIntegral b))

由于实例重叠而失败。

真的有可能创建一个合适的类型类和实例来让factorial 为任何Integral a 运行吗?还是会一直出现这个问题?

【问题讨论】:

  • 一种可能的解决方案是使用不可判定的实例。
  • @ThomasM.DuBuisson:上面的代码实际上已经使用了UndecidableInstances(虽然最初用于不同的东西,即class Booleanizeable b where toBool :: b -&gt; Bool 用于while,但它仍然存在)。
  • @Zeta 为了清楚起见,您将阶乘更改为具有 sig factorial :: Int -&gt; Int 并且编译失败?
  • 我认为真正的问题是您尝试做的事情几乎没有意义。将值与对这些值的引用混为一谈,即使你能以某种方式使其工作,也不过是麻烦。
  • 我现在不想写出一个完整的答案,所以我只是将您指向this amusing blog post,它为非常类似于 C 的 Haskell 提供了一个 DSL。它与@fizruk 的答案基本相似,但具有一些额外的聪明之处,可以使事物作为左值和右值正常工作。

标签: haskell


【解决方案1】:

想法

想法很简单:将STRef s a 包装在一个新的数据类型中,并使其成为Num 的实例。

解决方案

首先,我们只需要一个编译指示:

{-# LANGUAGE RankNTypes #-}

import Data.STRef    (STRef, newSTRef, readSTRef, modifySTRef)
import Control.Monad (when)
import Control.Monad.ST (ST, runST)

STRef 的包装器:

data MyRef s a
  = MySTRef (STRef s a)  -- reference (can modify)
  | MyVal a              -- pure value (modifications are ignored)

instance Num a => Num (MyRef s a) where
  fromInteger = MyVal . fromInteger

MyRef 的一些帮助函数类似于 STRef 函数:

newMyRef :: a -> ST s (MyRef s a)
newMyRef x = do
  ref <- newSTRef x
  return (MySTRef ref)

readMyRef :: MyRef s a -> ST s a
readMyRef (MySTRef x) = readSTRef x
readMyRef (MyVal   x) = return x

我想使用更通用的alter 助手来实现-=*=

alter :: (a -> a -> a) -> MyRef s a -> MyRef s a -> ST s ()
alter f (MySTRef x) (MySTRef y) = readSTRef y >>= modifySTRef x . flip f
alter f (MySTRef x) (MyVal   y) = modifySTRef x (flip f y)
alter _ _ _ = return ()

(-=) :: Num a => MyRef s a -> MyRef s a -> ST s ()
(-=) = alter (-)

(*=) :: Num a => MyRef s a -> MyRef s a -> ST s ()
(*=) = alter (*)

其他功能几乎不变:

var :: a -> ST s (MyRef s a)
var = newMyRef

def :: (forall s. ST s (MyRef s a)) -> a
def m = runST $ m >>= readMyRef

while :: (Num a, Ord a) => MyRef s a -> ST s () -> ST s ()
while i m = go
  where
    go = do
      n <- readMyRef i
      when (n > 0) $ m >> go

assert :: Monad m => Bool -> String -> m ()
assert b str = when (not b) $ error str

factorial :: Integral a => a -> a
factorial n = def $ do
    assert (n >= 0) "Negative factorial"
    ret <- var 1
    i   <- var n
    while i $ do
      ret *= i
      i -= 1
    return ret

main :: IO ()
main = print . factorial $ 1000

讨论

制作像这样的Num 实例感觉有点hacky,但是我们在Haskell 中没有FromInteger 类型类,所以我想没关系。

另一个很痒的地方是3 *= 10,也就是return ()。我认为可以使用幻像类型来指示MyRefST 还是纯的,并且只允许STalter 的LHS 上。

【讨论】:

  • 天哪。具有 两个 构造函数的类型。这真的很简单。我不应该尝试用太少的睡眠来模拟这样的事情,我尝试用newSTRef 来构建值,但是你最终会得到ST s (STRef s a)。缺少的 FromInteger 类是我非常希望在未来的 Haskell 版本中看到的东西。通常,您只想对非Num 类型使用文字,而最终得到hacky Num 实例,如您的答案所示。不管怎样,干得好。 (顺便说一句,newMyRef = fmap MySTRef . newSTRef
猜你喜欢
  • 1970-01-01
  • 2016-12-13
  • 1970-01-01
  • 2012-08-31
  • 1970-01-01
  • 2020-12-23
  • 2019-02-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多