【问题标题】:Polymorphic function to convert between two types用于在两种类型之间转换的多态函数
【发布时间】:2014-05-12 18:26:35
【问题描述】:

是否可以在 Haskell98 或扩展以下函数 f 中编写代码?

f :: (a -> b) -> (b -> a) -> c -> d
where (c,d) :: (a,b) || (c,d) :: (b,a)

我正在尝试创建一些通用函数来在ab 的任意两种类型之间进行转换,但我很难同时处理这两种类型的任意性。然而,它们立即由第一个参数a ->b 设置,所以我希望这是可能的。任何指导表示赞赏...

谢谢!

[编辑]

在我看来,我的问题归结为haskell的类型系统是否支持or关键字...因为我的函数的直观签名是:f :: (a->b) -> (b->a) -> a -> b or (a->b) -> (b->a) -> b -> a...

【问题讨论】:

  • 您能详细说明您希望如何使用这样的功能吗?
  • 实现更简洁的代码。大多数在a ->b 类型之间转换的函数要么是完全刚性的,要么仅在一种类型上是多态的。我可以用类型类来实现后者,但这次我不知道该怎么做。我想我可以使用多参数类型类并声明Instance a b,也许...
  • 这是为什么呢?在任何给定的情况下,您似乎都知道您正在应用转换 int 的哪个方向,并且可以酌情使用a2bb2a:您唯一使用convertAB 的时间是您不确定时您在哪个方向进行转换,这似乎是个坏主意。
  • 这和镜头包中使用isos有很大关系。我建议去那里看看,但要注意它可能是一个非常复杂的学习库。
  • 我不认为它会使代码更干净。一方面,你摆脱了一个名字;另一方面,您需要更多上下文来猜测函数在使用它读取代码时正在做什么。损失大于收益 IMO。

标签: haskell polymorphism


【解决方案1】:

正如 alecb 解释的那样,它不能按照您的签名建议的方式完成,至少通过普通方式(它需要在运行时检查类型)。您可能喜欢的另一种选择是来自lensIsoPrismIso 符合要求,如果您的转换函数是全双射的;如果其中一个功能是部分的,您可以使用 Prism 代替。这是一个一次性的例子:

import Control.Lens
import Text.ReadMaybe

stringInt :: Prism' String Int
stringInt = prism' show readMaybe

GHCi> "3" ^? stringInt
Just 3
it :: Maybe Int
GHCi> 3 ^. re stringInt
"3"
it :: String

【讨论】:

  • 这不是正确的同构。 "Hello!"Int 表示是什么?一个方向上的对应只是部分的,这使得它成为Prism 而不是Iso
  • @Carl 确实,以这种方式保留它是一种误导。我将示例更改为使用Prism
【解决方案2】:

我怀疑是否可以直接做你想做的事,但你可以使用一些解决方法:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}

class Converter a b c where
    ($$) :: a -> b -> c

data Conv a b = Conv a b

instance Converter (Conv (a -> b) (b -> a)) a b where
    ($$) (Conv f _) = f

instance Converter (Conv (a -> b) (b -> a)) b a where
    ($$) (Conv _ g) = g

intToString :: Int -> String
intToString = show

stringToInt :: String -> Int
stringToInt = read

printString :: String -> IO ()
printString = print

printInt :: Int -> IO ()
printInt = print

main = do
    let convert = Conv intToString stringToInt
    printString $ convert $$ (12345 :: Int)
    printInt $ convert $$ "12345"

我相信这已经足够接近你想要的了。唯一的区别是必须使用$$ 运算符,但这是不可避免的。

更新:您甚至可以删除特殊的 Conv 结构并使用普通元组:

instance Converter (a -> b, b -> a) a b where
    ($$) (f, _) = f

instance Converter (a -> b, b -> a) b a where
    ($$) (_, g) = g

-- ...
printString $ (intToString, stringToInt) $$ (12345 :: Int)
printInt $ (intToString, stringToInt) $$ "12345"

我认为这更接近您的要求。

【讨论】:

    【解决方案3】:

    假设我们有这两个转换函数:

    i2s :: Int -> String
    s2i :: String -> Int
    

    g = f i2s s2i 的类型是什么?我们希望g 1 成为一个合法的表达方式,也希望g "one"。所以g 接受IntString。这给我们留下了三个选择:

    1. g 是一个完全多态的函数——也就是说,它的第一个参数是a 类型。所以g Trueg [1,2,3] 也是合法的。如此泛型的函数与转换无关,你可以证明这类函数不能做任何有趣的事情(例如a -> Int类型的函数必须是常量)

    2. g 需要某个类型类的参数 - 例如,show 是一个仅接受有限类型组的函数(包括 IntString)。但是现在你的函数不是完全多态的——它只适用于这个受限类型组。

    3. 我们可以为这个任务定义一个新的数据类型:

      data IntOrString = I Int | S String
      
      -- the type signature is slightly different
      f :: (Int -> String) -> (String -> Int) -> IntOrString -> IntOrString
      f i2s s2i (I n) = S $ i2s n
      f i2s s2i (S s) = I $ s2i S
      

      虽然此函数在某些情况下可能有用,但它不再是通用的(它支持的唯一一对是 IntString)。

    所以答案是 - 您可以使用此类型签名定义一个函数,但它仅适用于特定类型或类型类。

    编辑:不,不支持a -> b where a is Int or String 类型的函数。这需要支持子类型化的动态语言或静态语言。

    【讨论】:

    • 这几乎是我想要的,但它有两个问题:1. 创建混合 data 类型 2. 输出 (IntOrString) 似乎不会自动转换为原始类型:(IntString)。对不起。
    【解决方案4】:

    我不完全确定您希望如何使用这样的功能,但我的直觉是不会。假设我们有:

    intToString :: Int -> String
    intToString = show
    
    stringToInt :: String -> Int
    stringToInt = read
    
    foo = f intToString stringToInt
    

    foo 的类型是什么?您必须能够将foo 应用于IntString,这在haskell 中通常是不可能的:您可以将foo 应用于其中之一,但不能同时应用于两者。

    编辑:感觉这不是故事的全部——foo 当然可以是foo :: c -> d 类型,但这将允许它应用于任何 em> 键入,而不仅仅是 IntString。当应用于错误的类型时,可能会引发错误。即使这样的事情是可能的,这听起来也不是一个好主意。

    【讨论】:

      猜你喜欢
      • 2014-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-16
      • 1970-01-01
      • 2019-02-16
      • 2014-02-08
      • 1970-01-01
      相关资源
      最近更新 更多