编写一个通用函数,其中包含两种类型的参数

时间:2012-02-24 20:38:47

标签: generics haskell

这是this one的后续问题。我想我在Haskell中误解了什么类型的意思,所以希望这是一个更好的问题表达方式:

我想要一个可以用两个参数调用的函数。这些参数必须是不同的类型。例如,一个是字符串,另一个是整数。

考虑这个应用程序:

combine "100" 500 -- results in 100500
combine 100 "500" -- results in 100500
combine 100 500 -- raises an exception
combine "100" "500" -- raises an exception

编写具体的实现不是问题,但是,对我来说,给这个函数一个合适的签名是一个问题。

我还有兴趣了解是否存在更通用的解决方案(即不需要指定具体类型,但只是规定类型不同。例如,您可以使用如果可以通过置换参数来修复输入,则将此函数“修复”到其他函数。

谢谢!

编辑:

下面是我在Erlang期待它做的不精确的副本......好吧,我希望它有意义,因为它应该非常相似...

combine([String], Int)->
    io:fwrite("~s~w~n", [[String], Int]);

combine(Int, [String])->
    combine([String], Int).

8 个答案:

答案 0 :(得分:7)

Sjoerd打败了我,但我更喜欢我的解决方案,所以无论如何我都会发布它。

{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}

module Foo where

class Combinable a b where
  combine :: a -> b -> Int

instance Combinable Int String where
  combine a b = read (show a ++ b)

instance Combinable String Int where
  combine a b = read (a ++ show b)

由于这不包含Combinable a a实例,因此尝试使用一个是编译时错误而不是运行时错误。

答案 1 :(得分:6)

我不是100%清楚为什么你想要这个。我提出的其他人没有提到的一种可能性是你只想要与命令无关的功能应用程序。这可以通过“记录应用程序”成语来实现。例如,您可能会写这样的内容:

data Argument = Argument { name :: String, age :: Int }
instance Default Argument where def = Argument def def

combine Argument { name = n, age = a } = name ++ " is " ++ show age ++ " years old"

然后您可以使用命名参数调用它:

combine def { name = "Daniel", age = 3 }
combine def { age = 3, name = "Daniel" }

名称甚至比检查类型不相等要好一些,因为您可以使用相同类型的多个参数而不会产生歧义。

data Name = Name { first, middle, last :: String }
instance Default Name where def = Name def def def

esquire n@(Name { last = l }) = n { last = l ++ ", Esquire" }

您可以像这两个一样调用,例如:

esquire def { first = "Daniel", middle = "M.", last = "Wagner" }
esquire def { last = "Wagner", first = "Daniel" }

答案 2 :(得分:6)

虽然其他答案是“写一个(稍微丑陋的)类”和“通过和类型统一类型”。我将提出一个非常非常Haskelly的建议并提醒大家,如果你要求Haskell确实有动态类型。

在运行时,只需询问类型是什么,并使每种类型的操作都不同。这可以使用Data.Typeable模块完成。

例如:

import Data.Typeable
import Data.Data

combine :: (Typeable a, Typeable b) => a -> b -> Int
combine a b
  | typeOf a == strTy && typeOf b == intTy =
      case (cast a, cast b) of
          (Just str,Just i) -> read $ str ++ show (i :: Int)
  | typeOf a == intTy && typeOf b == strTy =
      case (cast a, cast b) of
          (Just i,Just str) -> read $ show (i :: Int) ++ str
  | otherwise = error "You said you wanted an exception..."
 where
 strTy = typeOf ""
 intTy = typeOf (undefined :: Int)

测试运行显示:

> combine "100" (500 :: Int)
100500

如果你想摆脱异常那就太棒了!我们可以在使用Maybe monad时清理代码:

combine2 :: (Typeable a, Typeable b) => a -> b -> Maybe Int
combine2 a b
  | typeOf a == strTy && typeOf b == intTy = do
      a' <- cast a
      b' <- cast b
      return $ read $ a' ++ show (b' :: Int)
  | typeOf a == intTy && typeOf b == strTy = do
      a' <- cast a
      b' <- cast b
      return $ read $ show (a' :: Int) ++ b'
  | otherwise = Nothing
 where
 strTy = typeOf ""
 intTy = typeOf (undefined :: Int)

还有一些输出只是为了它的原因:

> combine2 "500" (5 :: Int)
Just 5005
> combine (5 :: Int) "500"
5500
> combine2 (5 :: Int) "500"
Just 5500
> combine "500" "300"
*** Exception: You said you wanted an exception...
> combine2 "500" "300"
Nothing

就是这样!我们可以添加我们想要的多种类型组合,只需在最后otherwise后卫之前插入所需的操作。

答案 3 :(得分:3)

我几乎肯定你不能做你描述的通用事物。在Haskell中,没有办法像你描述的那样表达对等式的否定。

你可能能够使用OverlappingInstances和多参数类型类做一些非常糟糕的黑客攻击,这会导致运行时错误而不是编译时错误,但这真的很丑陋而令人沮丧。 / p>

答案 4 :(得分:3)

Louis Wasserman提到的丑陋和令人沮丧的解决方案将是这样的:

{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}

class Combine a b where 
  combine :: a -> b -> String

instance Combine a a where
  combine = error "Types are the same"

instance (Show a, Show b) => Combine a b where
  combine a b = show a ++ show b

答案 5 :(得分:2)

另一个丑陋而令人沮丧的解决方案:

{-# FlexibleInstances, TypeSynonymInstances #-}

class IntOrString a where
  toString :: a -> String
  typeID :: a -> Int

instance IntOrString String where
  toString s = s
  typeID _ = 0    

instance IntOrString Int where
  toString x = show x
  typeID _ = 1    

combine a b | typeID a + typeID b == 1 = toString a ++ toString b
combine _ _ = error "WTF?!?"

combine "100" (500::Int) --type needed because of monomorphism restriction

答案 6 :(得分:1)

您必须创建自己的数据类型,因为haskell中不能包含未定义的类型。

data IntAndString = TypeA Int String | TypeB String Int

combine IntAndString -> string
combine TypeA(n s) = show n ++ s
combine TypeB(s n) = s ++ show n

组合可以用

调用
combine TypeA(Int String)

combine TypeB(String Int)

答案 7 :(得分:1)

  

编写具体实现不是问题,它是一个   但是,对我来说,要给这个功能一个合适的签名。

只要功能没有更高级别的类型,您就不需要。 Haskell会为你推断出这种类型。

那就是说,我觉得你想要的东西在Haskell中没有多大意义,在那里代码和数据严格分开¹以及运行时和编译时间,这与Lisp不同。什么是combine的用例?

¹当然,函数在某种意义上是数据,但它们只是完全不透明的常量。您无法在运行时操作函数。