代数的データ型または多型を使用してOOPの抽象クラスのHaskellの同等のは、ありますか?
質問
でHaskellは、それが二つの異なる(類似であるが)データ型を受け入れることができる署名で関数を作成し、
に渡された種類に応じて異なる動作させることが可能ですの例では、私の質問をより明確にすることがあります。私はmyFunction
という名前の関数を持っている、とMyTypeA
とMyTypeB
という名前の2つのタイプの場合、私はmyFunction
を定義することができ、それが唯一のその最初のパラメータとして型MyTypeA
やMyTypeB
のデータを受け入れることができるように?
type MyTypeA = (Int, Int, Char, Char)
type MyTypeB = ([Int], [Char])
myFunction :: MyTypeA_or_MyTypeB -> Char
myFunction constrainedToTypeA = something
myFunction constrainedToTypeB = somethingElse
OOP言語では、あなたは私がそうのように達成しようとしているものを書くことができます:
public abstract class ConstrainedType {
}
public class MyTypeA extends ConstrainedType {
...various members...
}
public class MyTypeB extends ConstrainedType {
...various members...
}
...
public Char myFunction(ConstrainedType a) {
if (a TypeOf MyTypeA) {
return doStuffA();
}
else if (a TypeOf MyTypeB) {
return doStuffB();
}
}
私は代数的データ型について読んでてきたと私はHaskellのの種類を定義する必要があると思うが、私は確信してどのようにそれが一つのタイプを保存することができるようにそれを定義については行かないんですか別の、そしてまた、私は私自身の機能でそれを使用する方法について説明します。
解決
はい、あなたは正しい、あなたは代数的データ型を探しています。彼らの偉大なチュートリアルでは、あなたを学びHaskellのでありますのます。
は記録のために、OOPから抽象クラスの概念は、実際にHaskellのに三つの異なる翻訳を持っている、とのADTはただ一つです。ここでは技術の概要です。
代数的データ型
は代数的データ型は、サブクラスが知られている抽象クラスのパターンを符号化、および機能は、オブジェクトがダウンキャスティングによってのメンバーである特定のどのインスタンスチェックここ。
abstract class IntBox { }
class Empty : IntBox { }
class Full : IntBox {
int inside;
Full(int inside) { this.inside = inside; }
}
int Get(IntBox a) {
if (a is Empty) { return 0; }
if (a is Full) { return ((Full)a).inside; }
error("IntBox not of expected type");
}
に変換ます:
data IntBox = Empty | Full Int
get :: IntBox -> Int
get Empty = 0
get (Full x) = x
機能の録音
上記Get
機能はこのスタイルで表現できることがないように、このスタイルは、ダウンキャストはできません。そこでここでは完全に異なるものです。
abstract class Animal {
abstract string CatchPhrase();
virtual void Speak() { print(CatchPhrase()); }
}
class Cat : Animal {
override string CatchPhrase() { return "Meow"; }
}
class Dog : Animal {
override string CatchPhrase() { return "Woof"; }
override void Speak() { print("Rowwrlrw"); }
}
Haskellではその翻訳は型に型をマップしません。 Animal
は唯一のタイプであり、かつDog
とCat
は、そのコンストラクタ関数に離れて押しつぶされます:
data Animal = Animal {
catchPhrase :: String,
speak :: IO ()
}
protoAnimal :: Animal
protoAnimal = Animal {
speak = putStrLn (catchPhrase protoAnimal)
}
cat :: Animal
cat = protoAnimal { catchPhrase = "Meow" }
dog :: Animal
dog = protoAnimal { catchPhrase = "Woof", speak = putStrLn "Rowwrlrw" }
この基本的な概念のいくつかの異なる順列があります。不変の抽象型は、メソッドは、レコードのフィールドですレコードタイプがあるということです。
EDIT:上記のコードのバグを含む、このアプローチの機微のいくつかのコメントでは良い議論が、あります。
型クラス
これはOOのアイデアの私の最も好きなエンコーディングです。それは身近な単語を使用し、型に型をマップするので、それはOOプログラマに快適です。しかし、機能の記録上記のアプローチは、物事が複雑になるときと作業が容易になる傾向がある。
私は再び動物の例をエンコードします。
class Animal a where
catchPhrase :: a -> String
speak :: a -> IO ()
speak a = putStrLn (catchPhrase a)
data Cat = Cat
instance Animal Cat where
catchPhrase Cat = "Meow"
data Dog = Dog
instance Animal Dog where
catchPhrase Dog = "Woof"
speak Dog = putStrLn "Rowwrlrw"
このルックスの素敵な、それはしないのですか?あなたはそれがOOのように見えるにもかかわらず、それは本当にOOのような作業をしないことを認識したときに困難が来ます。あなたは動物のリストを持っている場合がありますが、あなたが今できる最善のはAnimal a => [a]
、均質な動物のリスト、例えばあります。唯一の猫や犬だけのリスト。そして、あなたは、このラッパー型にする必要があります:
{-# LANGUAGE ExistentialQuantification #-}
data AnyAnimal = forall a. Animal a => AnyAnimal a
instance Animal AnyAnimal where
catchPhrase (AnyAnimal a) = catchPhrase a
speak (AnyAnimal a) = speak a
そして[AnyAnimal]
あなたは動物のあなたのリストについては、何をしたいです。しかし、それは第二の例ではAnyAnimal
レコードと自身に関する正確の同じ情報Animal
が公開にをことが判明し、私たちは遠回しにそれについて行ってきました。したがって、なぜ私は型クラスは、オブジェクト指向の非常に良いエンコーディングであることを考慮していない。
とのあまりにも多くの情報!の
の今週の版を締結他のヒント
それはあなたが型クラスの上に読みたいかもしれないように聞こえます。
の型クラスのを使用してこの例を考えてみましょう。
するためにMVC
MultiParamTypeClasses
tState
:3種類(ノートtAction
)に基づく「抽象クラス」tReaction
のような - 私たちは、C ++を定義します
アクションが状態に適用した場合、あなたは新しい状態と反応を得る(キー機能tState -> tAction -> (tState, tReaction)
を定義します。
型クラスではあり
3「C ++抽象」機能、およびいくつかのより多くの「抽象的」なものに定義されています。必要なときとinstance MVC
「抽象的」機能が定義されます。
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, NoMonomorphismRestriction #-}
-- -------------------------------------------------------------------------------
class MVC tState tAction tReaction | tState -> tAction tReaction where
changeState :: tState -> tAction -> tState -- get a new state given the current state and an action ("abstract")
whatReaction :: tState -> tReaction -- get the reaction given a new state ("abstract")
view :: (tState, tReaction) -> IO () -- show a state and reaction pair ("abstract")
-- get a new state and a reaction given an state and an action (defined using previous functions)
runModel :: tState -> tAction -> (tState, tReaction)
runModel s a = let
ns = (changeState s a)
r = (whatReaction ns)
in (ns, r)
-- get a new state given the current state and an action, calling 'view' in the middle (defined using previous functions)
run :: tState -> tAction -> IO tState
run s a = do
let (s', r) = runModel s a
view (s', r)
return s'
-- get a new state given the current state and a function 'getAction' that provides actions from "the user" (defined using previous functions)
control :: tState -> IO (Maybe tAction) -> IO tState
control s getAction = do
ma <- getAction
case ma of
Nothing -> return s
Just a -> do
ns <- run s a
control ns getAction
-- -------------------------------------------------------------------------------
-- concrete instance for MVC, where
-- tState=Int tAction=Char ('u' 'd') tReaction=Char ('z' 'p' 'n')
-- Define here the "abstract" functions
instance MVC Int Char Char where
changeState i c
| c == 'u' = i+1 -- up: add 1 to state
| c == 'd' = i-1 -- down: add -1 to state
| otherwise = i -- no change in state
whatReaction i
| i == 0 = 'z' -- reaction is zero if state is 0
| i < 0 = 'n' -- reaction is negative if state < 0
| otherwise = 'p' -- reaction is positive if state > 0
view (s, r) = do
putStrLn $ "view: state=" ++ (show s) ++ " reaction=" ++ (show r) ++ "\n"
--
-- define here the function "asking the user"
getAChar :: IO (Maybe Char) -- return (Just a char) or Nothing when 'x' (exit) is typed
getAChar = do
putStrLn "?"
str <- getLine
putStrLn ""
let c = str !! 0
case c of
'x' -> return Nothing
_ -> return (Just c)
-- --------------------------------------------------------------------------------------------
-- --------------------------------------------------------------------------------------------
-- call 'control' giving the initial state and the "input from the user" function
finalState = control 0 getAChar :: IO Int
--
main = do
s <- finalState
print s