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

5 generics haskell

这是对此问题的后续问题.我想我在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
Run Code Online (Sandbox Code Playgroud)

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

我也有兴趣了解是否有一个更通用的解决方案(即不需要指定具体类型,但只是规定类型不同.所以,例如,你可以使用这个函数来如果可以通过置换参数来修复输入,则"修复"其他函数的输入.

谢谢!

编辑:

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

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

combine(Int, [String])->
    combine([String], Int).
Run Code Online (Sandbox Code Playgroud)

Joh*_*n L 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)
Run Code Online (Sandbox Code Playgroud)

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


Dan*_*ner 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"
Run Code Online (Sandbox Code Playgroud)

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

combine def { name = "Daniel", age = 3 }
combine def { age = 3, name = "Daniel" }
Run Code Online (Sandbox Code Playgroud)

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

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" }
Run Code Online (Sandbox Code Playgroud)

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

esquire def { first = "Daniel", middle = "M.", last = "Wagner" }
esquire def { last = "Wagner", first = "Daniel" }
Run Code Online (Sandbox Code Playgroud)


Tho*_*son 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)
Run Code Online (Sandbox Code Playgroud)

测试运行显示:

> combine "100" (500 :: Int)
100500
Run Code Online (Sandbox Code Playgroud)

如果你想摆脱异常那么棒!我们可以使用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)
Run Code Online (Sandbox Code Playgroud)

还有一些产品只是为了它的产生:

> 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
Run Code Online (Sandbox Code Playgroud)

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