Write a generic function with exactly two variations of types of parameters

This is a follow up question to this one. I think I misunderstood a little bit what type is meant to do in Haskell, so here's, hopefully, a better formulation of the question:

I want to have a function that can be called with exactly two arguments. These arguments must be of different types. For example, one is string, and another is an integer.

Consider this application:

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

It is not a problem to write a concrete implementation, it is a problem, however, for me, to give this function a proper signature.

I would be also interested to learn whether there is a solution that is more generic (ie doesn't require to specify the concrete types, but only prescribes for the types to be different. So that, for example, you could use this function to "fix" the input to other functions, if it can be fixed by permuting the arguments.

Thank you!

EDIT:

below is an imprecise copy of what I was expecting it to do in Erlang... well, I hope it makes sense, since it should be quite similar...

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

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

Sjoerd beat me to it, but I prefer my solution so I'll post it anyway.

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

Since this doesn't include a Combinable aa instance, trying to use one is a compile-time error rather than a runtime error.


It's not 100% clear to me why you want this. One possibility I came up with that others hadn't mentioned is that you simply want order-agnostic function application. This is possible with the "record application" idiom. For example, you might write something like this:

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"

You can then call it with named arguments:

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

Names are even just a tad better than checking that the types aren't equal, because you can have multiple arguments with the same type without ambiguity.

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

Which you can call like these two, for example:

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

While the other answers are "write a (slightly ugly) class" and "unify the types via an sum type". I'm going to make a not-very-Haskelly suggestion and remind everyone that Haskell does have dynamic typing if you ask for it.

At run-time, just ASK what the type is and make your operation different for each type. This can be done using the Data.Typeable module.

For example:

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)

And a test run shows:

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

If you want to get rid of the exception then great! We can clean up the code using the Maybe monad while we're at it:

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)

And some more output just for the heck of it:

> 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

And that's it! We can add how ever many of combinations of types we want, just insert your desired operations before the last otherwise guard.

链接地址: http://www.djcxy.com/p/43322.html

上一篇: 如何返回Haskell中的元组列表

下一篇: 编写一个通用函数,其中包含两种参数类型的变体