2012-02-24 1 views
5

これはthis oneへのフォローアップの質問です。私はHaskellでどんなタイプのことを誤解していたと思うので、うまくいけば、質問のより良い形式ができあがります:パラメータの種類のちょうど2つのバリエーションを持つジェネリック関数を書く

ちょうど2つの引数で呼び出すことができる関数が必要です。これらの引数は、異なる型でなければなりません。たとえば、1つは文字列、もう1つは整数です。

は、このアプリケーション考えてみましょう:

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

具体的な実装を書くための問題ではありません、それはこの機能に適切な署名を与えるために、私にとっては、しかし、問題となっています。

さらに一般的な解決策があるかどうかを知ることも興味があります(具体的な種類を指定する必要はなく、種類が異なるようにのみ指定します)。それは、引数を置換して固定することができれば、他の関数への入力を「修正」するために、この機能を使用

ありがとう

EDIT:。!下記

は私が期待していた何の不正確なコピーでありますそれはErlangでやるべきこと...まあ、それはかなり似ているはずなので、意味があると思います...

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

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

それはタイプの問題ですので、あなたは「例外を発生させる」のは嫌だ、あなたはコンパイル時エラーをしたいと思います。違いはペタニックに聞こえるかもしれませんが、重要です。 Haskellでは、可能であればコンパイル時エラーが常に必要です。また、ハスケルの代わりにPythonで考えているように思えます:P –

+3

これを行う正しい方法は 'combine :: Integer - > String - > String'です。これは、各タイプの引数を1つだけ取ることを保証します。それは簡単です、それは明らかです。高度な機能は含まれていません。何を求めることができますか? – Carl

+0

私は個人的には、単にcombineStrInt :: String-> Int-> Int'と 'combineIntStr :: Int-> String-> Int'という2つの関数を作ることをお勧めします。私は*ただ一つの関数を推奨し、必要に応じて引数の順序を逆にするために 'flip'を使いますが、あなたの例ではパラメータを反転するだけで正しい結果が得られません。実際には、おそらく 'flip'が役に立つでしょう。 –

答えて

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インスタンスが含まれていないので、いずれかを使用しようとすると、コンパイル時エラーではなく、実行時エラーです。

3

あなたが説明している一般的なことを行うことができないと私はほとんど肯定的です。ハスケルには、あなたのような平等の否定を表現する方法はありません。

OverlappingInstancesと複数パラメータタイプのクラスを使用すると、実際に汚いハックを行うことができますが、コンパイル時エラーではなくランタイムエラーが発生しますが、それは本当に醜いものです。

+0

+1醜いとうつ病。 –

+0

実際、Haskellでは、複数パラメータ型のクラス/関数の依存関係/ etcを介して型の不等式を強制し、コンパイル時のエラーを得ることができます。 "オレグは既にそれをやった"膨大な量のプログラミングがこれに依存しています。私が知っている限り、これは強制することができる唯一の負の制約です。タイプファミリーと組み合わせると、明らかにOWSの違反行為でもあります。 –

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 

缶eiterを組み合わせは

combine TypeA(Int String) 

または

combine TypeB(String Int) 
+0

私は解決策が気に入っているので、Upvotedですが、ハスケルがジェネリック型を持つことができないということはどういう意味ですか? –

+1

それは私によって間違っていた。タイプはgeniricにすることができますが、関数は汎用ではありません – nist

3

ルイ・ワッサーマンはこのようなものになるだろう言及した醜いと憂鬱溶液で呼び出すこと:

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

GHCがそれを飲み込ませるには、おそらくOverlappingInstancesが必要です。 –

+2

@LouisWasserman驚いたことにあなたはしません。あなたが 'combine :: a - > a - > String'を使用する状況でそれを使用しない限り。 GHCのオーバーラップチェックは怠惰で、必要になるまでトリガしません。 –

+0

私たちの答えは本質的に同じです。唯一の違いは、私たちが定義したインスタンスです。多くのハスケラーは、多数のインスタンス宣言を書くことへの嫌悪感を持っています(私のアプローチは、より多くの型を追加すると宣言の爆発につながります)。しかし、IMHOは時には最良の方法です。 –

1

具体的な実装を書くのは問題ではありませんが、私にとってはこの機能に適切な署名を付けることは問題ですが、 問題です。

関数の上位ランクの型がない限り、必要はありません。ハスケルはあなたのためにタイプを推論します。

つまり、Lispとは異なり、コードとデータが厳密に区切られているだけでなく、実行時とコンパイル時にも、Haskellでは意味をなさないと感じています。 combineのユースケースは何ですか?

¹もちろん、関数は意味のあるデータですが、関数は完全に不透明な定数です。実行時に関数を操作することはできません。

+1

それは簡単です:それはありません。ハスケルでの複数の発送などはありません。 – Ingo

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 [email protected](Name { last = l }) = n { last = l ++ ", Esquire" } 

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

ながら他の回答している「(やや醜い)クラスを作成」と「和タイプ経由の種類を統一」。私は非常にハスケルではない提案を行い、あなたがそれを求めるならば、ハスケルは動的な型定義を持っていることを皆に思い出させます。

実行時には、その型が何であるかをASKし、型ごとに操作を異ならせます。これは、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 

あなたは偉大な例外を取り除きたい場合は!

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ガードの前にあなたの望む操作を挿入してください。

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 
関連する問題