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

Generics 编写一个泛型函数,其中包含两种类型的参数,generics,haskell,Generics,Haskell,这是我们的后续问题。我想我有点误解了Haskell中的类型,因此,希望这里有一个更好的问题表述: 我想有一个函数,可以用两个参数调用。这些参数必须是不同类型的。例如,一个是字符串,另一个是整数 考虑此应用程序: combine "100" 500 -- results in 100500 combine 100 "500" -- results in 100500 combine 100 500 -- raises an exception combine "100" "500" -- rais

这是我们的后续问题。我想我有点误解了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).

我几乎可以肯定的是,你不能做你所描述的一般的事情。在哈斯克尔,没有办法表达你所描述的对等式的否定


您可能可以使用重叠实例和多参数类型类来进行一些非常肮脏的攻击,这将导致运行时错误而不是编译时错误,但这将非常丑陋和令人沮丧。

您必须创建自己的数据类型,因为在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)


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
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"

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)
由于这不包括可组合的实例,因此尝试使用一个实例是编译时错误,而不是运行时错误

写一个具体的实现不是问题,而是一个问题 然而,对我来说,给这个函数一个正确的签名是一个问题

只要函数没有更高级别的类型,您就不需要。Haskell将为您推断类型

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

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

我不完全清楚您为什么要这样做。我提出了一种其他人没有提到的可能性,就是您只需要顺序无关的函数应用程序。这在“录制应用程序”习惯用法。例如,您可以编写如下内容:

{-# 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
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" }
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)

其他答案是“编写一个(稍微难看的)类”和“通过求和类型统一类型”。我将提出一个不太好的Haskell建议,并提醒大家,如果你要求的话,Haskell确实有动态类型

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

例如:

esquire def { first = "Daniel", middle = "M.", last = "Wagner" }
esquire def { last = "Wagner", first = "Daniel" }
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)

就这样!我们可以添加任意数量的类型组合,只需在最后一个
之前插入所需的操作,否则
保护。

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

{-# 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

因为这是类型的问题,所以您不想“引发异常”",您可能会想要一个编译时错误。这种差异听起来可能很迂腐,但很重要。在Haskell中,如果可能的话,我们总是想要编译时错误。而且,听起来您是在用Python而不是Haskell思考问题:PI会说正确的方法是编写:
combine::Integer->String->String
。这保证了需要exa每种类型都有一个参数。很简单,很清楚。它不涉及任何高级功能。你还能要求什么?我个人建议只做两个函数:
combineStrInt::String->Int->Int
combineIntStr::Int->String->Int
。我只推荐一个函数,然后使用
>flip
在需要时反转参数顺序,但对于您的示例,简单地翻转参数不会产生正确的结果。在实践中,您可能会发现
flip
很有用。您可能需要重叠实例才能使GHC吞下它。@LouisWasserman令人惊讶的是,您不会。除非您在特定情况下使用它在这一点上,可能会使用
combine::a->a->String
。GHC的重叠检查是惰性的,直到需要时才会触发。我们的答案基本相同;唯一的区别是我们选择定义的实例。许多Haskeller不喜欢编写大量的实例DECL(如果您开始添加更多类型,我的方法将导致DECL激增),但有时这是最好的方法。因为我喜欢这个解决方案,所以投票表决,但你说Haskell不能有泛型类型是什么意思?我错了。类型可以是generic的,但函数不能是generic的。这很简单:不容易。Haskell中没有多重分派。实际上,你可以在Haskell vi中强制执行类型不平等一个多参数类型类/函数依赖项/etc并得到编译时错误。“Oleg已经做到了。”大量类型级编程依赖于此。据我所知,这是唯一一种可以强制执行的负面约束