gpt4 book ai didi

haskell - 具有IO的Haskell多变量函数

转载 作者:行者123 更新时间:2023-12-02 08:10:08 24 4
gpt4 key购买 nike

是否可以有一个函数接受一个外部函数调用,而该外部函数的某些参数是CString并返回一个接受String的函数呢?

这是我要寻找的示例:

 foreign_func_1 :: (CDouble -> CString -> IO())
foreign_func_2 :: (CDouble -> CDouble -> CString -> IO ())

externalFunc1 :: (Double -> String -> IO())
externalFunc1 = myFunc foreign_func_1

externalFunc2 :: (Double -> Double -> String -> IO())
externalFunc2 = myFunc foreign_func_2

我想出了如何使用C数字类型执行此操作。但是,我想不出一种允许字符串转换的方法。

这个问题似乎适合IO函数,因为所有转换为CString的东西(例如newCString或withCString)都是IO。

这是代码看起来像只处理转换双精度的样子。
class CConvertable interiorArgs exteriorArgs where
convertArgs :: (Ptr OtherIrrelevantType -> interiorArgs) -> exteriorArgs

instance CConvertable (IO ()) (Ptr OtherIrrelevantType -> IO ()) where
convertArgs = doSomeOtherThingsThatArentCausingProblems
instance (Real b, Fractional a, CConvertable intArgs extArgs) => CConvertable (a->intArgs) (b->extArgs) where
convertArgs op x= convertArgs (\ctx -> op ctx (realToFrac x))

最佳答案

是否可以有一个函数接受一个外部函数调用,而该外部函数的某些参数是CString并返回一个接受String的函数呢?

你有可能吗?

<lambdabot> The answer is: Yes! Haskell can do that.

好。好东西我们得到了解决。

准备一些繁琐的手续:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

嗯,还不错。看,妈,没有重叠!

这个问题似乎适合IO函数,因为所有转换为CString的东西(例如newCString或withCString)都是IO。

对。这里要注意的事情是,我们需要关注两个相互关联的问题:两种类型之间的对应关系,允许进行转换;以及通过转换引入的任何其他上下文。为了充分处理此问题,我们将使这两个部分都明确并适当地改组。我们还需要注意差异。提升整个功能需要同时使用协变和逆变位置的类型,因此我们需要双向转换。

现在,给定我们要翻译的功能,该计划将如下所示:
  • 转换函数的参数,接收新的类型和某些上下文。
  • 将上下文推迟到函数的结果上,以获取所需参数。
  • 在可能的情况下折叠冗余上下文
  • 递归翻译函数的结果,以处理多参数函数

  • 好吧,听起来并不困难。首先,明确的上下文:

    class (Functor f, Cxt t ~ f) => Context (f :: * -> *) t where
    type Collapse t :: *
    type Cxt t :: * -> *
    collapse :: t -> Collapse t

    这表示我们有一个上下文 f,并且有些带有该上下文的 tCxt类型函数从 t中提取纯上下文,如果可能, Collapse尝试合并上下文。 collapse函数使我们可以使用类型函数的结果。

    目前,我们拥有纯上下文和 IO:

    newtype PureCxt a = PureCxt { unwrapPure :: a }

    instance Context IO (IO (PureCxt a)) where
    type Collapse (IO (PureCxt a)) = IO a
    type Cxt (IO (PureCxt a)) = IO
    collapse = fmap unwrapPure

    {- more instances here... -}

    很简单。处理上下文的各种组合有点繁琐,但是实例很明显并且易于编写。

    我们还需要一种方法来确定给定转换类型的上下文。目前,在任何一个方向上上下文都是相同的,但是可以肯定的是,在其他方面也是如此,因此我将它们分开对待。因此,我们有两个类型族,为导入/导出转换提供新的最外层上下文:

    type family ExpCxt int :: * -> *
    type family ImpCxt ext :: * -> *

    一些示例实例:

    type instance ExpCxt () = PureCxt
    type instance ImpCxt () = PureCxt

    type instance ExpCxt String = IO
    type instance ImpCxt CString = IO

    接下来,转换单个类型。我们稍后会担心递归。时间到另一个类型类:

    class (Foreign int ~ ext, Native ext ~ int) => Convert ext int where
    type Foreign int :: *
    type Native ext :: *
    toForeign :: int -> ExpCxt int ext
    toNative :: ext -> ImpCxt ext int

    这表示 extint这两种类型可以彼此唯一地转换。我意识到,对于每种类型始终只具有一个映射可能并不理想,但是我不希望进一步使事情复杂化(至少现在还不行)。

    如前所述,我在这里还推迟了处理递归转换。也许可以将它们组合在一起,但是我觉得这样会更清楚。非递归转换具有简单,定义明确的映射,这些映射引入了相应的上下文,而递归转换则需要传播和合并上下文,并处理与基本情况不同的递归步骤。

    哦,到现在为止,您可能已经注意到在课堂环境中有趣的摇摆式波浪号业务。这表明两种类型必须相等的约束。在这种情况下,它将每个类型函数与相反的类型参数相关联,从而提供了上述双向特性。嗯,不过您可能想拥有一个相当新的GHC。在较旧的GHC上,这将需要功能依赖性,并将其编写为 class Convert ext int | ext -> int, int -> ext

    术语级转换函数非常简单-在结果中注意类型函数的应用;应用程序一如既往地是左关联的,因此仅应用早期类型族的上下文。还要注意名称的交叉,因为导出上下文来自使用本机类型的查找。

    因此,我们可以转换不需要 IO的类型:

    instance Convert CDouble Double where
    type Foreign Double = CDouble
    type Native CDouble = Double
    toForeign = pure . realToFrac
    toNative = pure . realToFrac

    ...以及具有以下功能的类型:

    instance Convert CString String where
    type Foreign String = CString
    type Native CString = String
    toForeign = newCString
    toNative = peekCString

    现在来解决问题的核心,并递归翻译整个功能。引入另一个类型类也就不足为奇了。实际上,有两个,因为这次我分开了导入/导出转换。

    class FFImport ext where
    type Import ext :: *
    ffImport :: ext -> Import ext

    class FFExport int where
    type Export int :: *
    ffExport :: int -> Export int

    这里没什么有趣的。您可能现在已经注意到一种常见的模式-我们在术语和类型级别上进行的计算量大致相等,并且我们正在串联进行它们,甚至模仿名称和表达式结构。如果您要对涉及实际值的事物进行类型级别的计算,这是很常见的,因为如果GHC无法理解您的操作,就会变得很挑剔。像这样排列东西可以大大减少头痛。

    无论如何,对于这些类中的每一个,对于每种可能的基本情况,我们需要一个实例,对于递归情况,则需要一个实例。 las,由于通常令人讨厌的带有重叠的废话,我们不能轻易拥有通用的基本情况。可以使用fundeps和类型相等条件语句来完成,但是...呃。也许以后。另一个选择是通过提供所需转换深度的类型级别数字对转换函数进行参数化,其缺点是自动化程度较低,但也可以通过显式获得一些好处,例如不太可能偶然发现多态或模棱两可的类型。

    现在,我将假设每个函数都以 IO结尾,因为 IO aa -> b可以区分且没有重叠。

    首先,基本情况:

    instance ( Context IO (IO (ImpCxt a (Native a)))
    , Convert a (Native a)
    ) => FFImport (IO a) where
    type Import (IO a) = Collapse (IO (ImpCxt a (Native a)))
    ffImport x = collapse $ toNative <$> x

    这里的约束使用已知的实例来声明特定的上下文,并且我们具有一些基本类型并进行了转换。同样,请注意类型函数 Import和术语函数 ffImport共享的并行结构。这里的实际想法应该很明显-我们将转换函数映射到 IO上,创建某种嵌套上下文,然后再使用 Collapse / collapse进行清理。

    递归的情况类似,但更复杂:

    instance ( FFImport b, Convert a (Native a)
    , Context (ExpCxt (Native a)) (ExpCxt (Native a) (Import b))
    ) => FFImport (a -> b) where
    type Import (a -> b) = Native a -> Collapse (ExpCxt (Native a) (Import b))
    ffImport f x = collapse $ ffImport . f <$> toForeign x

    我们为递归调用添加了一个 FFImport约束,并且由于我们不清楚确切的含义,因此仅进行足够的确定以确保可以对其进行处理,因此上下文冲突变得更加尴尬。还要注意这里的矛盾,因为我们将函数转换为本地类型,但是将参数转换为外部类型。除此之外,它仍然非常简单。

    现在,我在这里省略了一些实例,但是其他所有内容都遵循与上述相同的模式,因此让我们跳到最后,对商品进行分类。一些虚构的外函数:

    foreign_1 :: (CDouble -> CString -> CString -> IO ())
    foreign_1 = undefined

    foreign_2 :: (CDouble -> SizedArray a -> IO CString)
    foreign_2 = undefined

    转换:

    imported1 = ffImport foreign_1
    imported2 = ffImport foreign_2

    什么,没有类型签名?奏效了吗?

    > :t imported1
    imported1 :: Double -> String -> [Char] -> IO ()
    > :t imported2
    imported2 :: Foreign.Storable.Storable a => Double -> AsArray a -> IO [Char]

    是的,这就是推断的类型。啊,那就是我想要看到的。

    编辑:对于任何想尝试一下的人,我已经在这里拿了完整的代码来进行演示,并对其进行了一些清理,然后添加了 uploaded it to github

    关于haskell - 具有IO的Haskell多变量函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7030476/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com