将具有有效函数空间(如 ML)的语言核心嵌入到 Haskell 中有多实用?

发布于 2024-09-13 01:34:38 字数 2092 浏览 8 评论 0原文

正如 Moggi 20 年前提出的那样,像 ML 这样的语言的有效函数空间 -> 可以分解为标准的总函数空间 => 加上一个强大的 monad T 捕捉效果。

<代码>A - > B 分解为 A =>; (TB)

现在,Haskell 支持 monad,包括一个 IO monad,它看起来足以实现 ML 中的效果,并且它有一个包含 => 的函数空间。 (但也包括部分功能)。因此,我们应该能够通过这种分解将 ML 的相当大的片段翻译成 Haskell。理论上我认为这是可行的。

我的问题是这样的嵌入是否可行:是否有可能设计一个 Haskell 库,允许以与 ML 不太远的风格在 Haskell 中进行编程?如果是的话,表现会如何?

我对“实用”的标准是,广泛使用效果的现有 ML 代码可以通过嵌入相对轻松地转录为 Haskell,包括涉及高阶函数的复杂情况。

为了使这一点具体化,我自己尝试通过嵌入进行这样的转录如下。主要功能是一些简单的 ML 代码的转录,该代码强制生成 5 个不同的变量名称。我的版本没有直接使用分解,而是提升了函数,以便它们评估其参数 - main 之前的定义是一个包含提升原语的迷你库。这工作得很好,但有些方面并不完全令人满意。

  1. 通过 val 将值注入到计算中的语法噪音有点太多。拥有未提升的函数版本(如 rdV)会有所帮助,但代价是需要定义这些函数。
  2. varNum 这样的非值定义需要通过 do 中的 <- 进行一元绑定。然后,这会强制依赖于它们的任何定义也位于同一 do 表达式中。
  3. 看起来整个程序最终可能会出现在一个巨大的 do 表达式中。这就是 ML 程序通常被考虑的方式,但在 Haskell 中它并没有得到很好的支持 - 例如,你被迫使用 case 而不是方程。
  4. 我想尽管整个 IO monad 都是线程化的,但还是会有一些懒惰。鉴于机器学习程序是为严格评估而设计的,因此惰性可能应该被消除。我不确定最好的方法是什么。

那么,对于改进这一点,或者使用相同分解的更好方法,甚至使用反映 ML 的风格在 Haskell 中实现相同广泛编程目标的不同方法,有什么建议吗? (并不是我不喜欢 Haskell 的风格,只是我希望能够轻松映射现有的 ML 代码。)

import Data.IORef
import Control.Monad

val :: Monad m => a -> m a
val = return

ref = join . liftM newIORef
rdV = readIORef                                    -- Unlifted, hence takes a value
(!=) r x =  do { rr <- r; xx <- x; writeIORef rr xx  }

(.+),(.-) :: IO Int -> IO Int -> IO Int
( (.+),(.-) ) = ( liftM2(+), liftM2(-) )

(.:) :: IO a -> IO [a] -> IO [a]
(.:) = liftM2(:)
showIO :: Show a => IO a -> IO String
showIO = liftM show

main = do 
    varNum <- ref (val 0)
    let newVar = (=<<) $ \() -> val varNum != (rdV varNum .+ val 1) >> 
                                val 'v' .: (showIO (rdV varNum))
    let gen = (=<<) $ \n -> case n of 0 -> return []
                                      nn -> (newVar $ val ()) .: (gen (val n .- val 1))
    gen (val 5)

As Moggi proposed 20 years ago, the effectful function space -> of languages like ML can be decomposed into the standard total function space => plus a strong monad T to capture effects.

A -> B decomposes to A => (T B)

Now, Haskell supports monads, including an IO monad that appears sufficient for the effects in ML, and it has a function space that contains => (but also includes partial functions). So, we should be able to translate a considerable fragment of ML into Haskell via this decomposition. In theory I think this works.

My question is whether an embedding like this can be practical: is it possible to design a Haskell library that allows programming in Haskell in a style not too far from ML? And if so how will the performance be?

My criteria for "practical" is that existing ML code with extensive use of effects could be relatively easily transcribed into Haskell via the embedding, including complicated cases involving higher-order functions.

To make this concrete, my own attempt at such a transcription via the embedding is below. The main function is a transcription of some simple ML code that imperatively generates 5 distinct variable names. Rather than use the decomposition directly, my version lifts functions so that they evaluate their arguments - the definitions prior to main are a mini-library including lifted primitives. This works okay, but some aspects aren't totally satisfactory.

  1. There's a little too much syntactic noise for the injection of values into computations via val. Having unlifted versions of functions (like rdV) would help this, at the cost of requiring these to be defined.
  2. Non-value definitions like varNum require monadic binding via <- in a do. This then forces any definitions that depend on them to also be in the same do expression.
  3. It seems then that the whole program might end up being in one huge do expression. This is how ML programs are often considered, but in Haskell it's not quite as well supported - e.g., you're forced to use case instead of equations.
  4. I guess there will be some laziness despite threading the IO monad throughout. Given that the ML program would be designed for strict evaluation, the laziness should probably be removed. I'm uncertain what the best way to do this is though.

So, any advice on improving this, or on better approaches using the same decomposition, or even quite different ways of achieving the same broad goal of programming in Haskell using a style that mirrors ML?
(It's not that I dislike the style of Haskell, it's just that I'd like to be able to map existing ML code easily.)

import Data.IORef
import Control.Monad

val :: Monad m => a -> m a
val = return

ref = join . liftM newIORef
rdV = readIORef                                    -- Unlifted, hence takes a value
(!=) r x =  do { rr <- r; xx <- x; writeIORef rr xx  }

(.+),(.-) :: IO Int -> IO Int -> IO Int
( (.+),(.-) ) = ( liftM2(+), liftM2(-) )

(.:) :: IO a -> IO [a] -> IO [a]
(.:) = liftM2(:)
showIO :: Show a => IO a -> IO String
showIO = liftM show

main = do 
    varNum <- ref (val 0)
    let newVar = (=<<) $ \() -> val varNum != (rdV varNum .+ val 1) >> 
                                val 'v' .: (showIO (rdV varNum))
    let gen = (=<<) $ \n -> case n of 0 -> return []
                                      nn -> (newVar $ val ()) .: (gen (val n .- val 1))
    gen (val 5)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

攒眉千度 2024-09-20 01:34:38

这里是 sigfpe 提供的一种可能的方法。它不涵盖 lambda,但似乎可以扩展到它们。

Here's a possible way, by sigfpe. It doesn't cover lambdas, but it seems it can be extended to them.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文