データがあり、これが整数なのか文字列なのかわからなかったが、(+1) を適用したかった場合、それが整数であれば素晴らしいが、文字列であれば何もしない場合、どのように処理しますか?これ?出番はここNothing
ですか?
3 に答える
Haskell 関数は厳密に型指定されています。つまり、入力と出力の型を指定します。そもそも複数の型の値を受け入れるためにも、Either
それらを同じ型内に保持するために使用する必要があります。したがって、たとえば、String
またはのいずれかを受け取りたい場合Integer
、関数には次の型が必要です。
f :: Either String Integer -> ...
次に、関数を記述する方法は、 でパターン マッチを行い、Either
受け取った値の型を確認することです。次のように記述します。
-- str is a String
f (Left str) -> ...
-- int is an Integer
f (Right int) -> ...
したがって、必要なことを行う最も簡単な方法は、数値が の場合にのみ増分しInteger
、そのままにしておくことString
です。次のように記述します。
-- Don't do anything if it is a string
f (Left str) = Left str
-- Increment it if it is an integer
f (Right int) = Right (int + 1)
上記の関数の型を推論するようにコンパイラに依頼すると、次のようになります。
f :: Either String Int -> Either String Int
Haskell にはfmap
、Functor
クラスから使用する上記のボイラープレートを回避するための優れたトリックがあります。これにより、次のように、半分にあるものを完全に無視しながら、Right
半分だけに関数を自動的に書き込むことができます。Either
Left
f = fmap (+1)
上記の関数の型を推測すると、実際には次のようになります。
f :: (Functor f, Num a) => f a -> f a
a
しかし、私たちの場合、 toInteger
とf
to を設定することで型を特殊化できますEither String
:
f :: Either String Int -> Either String Int
つまり、多数あるクラスEither String
のインスタンスの一例です。Functor
ただし、この関数を整数または文字列で使用するには、まずそれらをLeft
またはRight
コンストラクターでラップする必要があることに注意してください。たとえば、これらは型チェックされません。
f 1 -- WRONG!
f "a" -- WRONG!
しかし、これらは次のようになります。
f (Right 1) -- Correct!
f (Left "a") -- Correct!
これは、たとえば、整数と文字列のリストがある場合、次のように記述する必要があることを意味します。
list = [Right 1, Left "a", Right 2]
リスト内で整数と文字列を混在させようとすると、型エラーが発生することに注意してください。
list = [1, "a", 2] -- WRONG!
次にf
、最初の正しいものをマッピングして、次list
を取得できます。
map f list = [Right 2, Left "a", Right 3]
タイプを混ぜたくない
あなたは言う
リストがあるとしますが、リスト内の正確なタイプを知らずにいくつかの関数を適用したい場合、このリストは ["a"] または [1] の可能性があります。
Haskell では、そのようなリストを作成できません。リスト内のすべての要素は同じ型でなければなりません。[1,2,3]
またはを使用できますが、 は使用でき["a","cheese","4"]
ません["a",1]
。Haskell は、リスト内の要素で機能する関数のみを適用するようにします。Haskell とあなたの両方が、リスト内の要素の型をいつでも解決できます。この型の知識は静的型付けと呼ばれ、最初は不必要に厳格に見えますが、実際には非常に柔軟です。ゴールデン ルールは次のとおりです。コンパイラは、常にどのようなデータがあるかを認識しています。
型の問題をどのように処理できますか?
このようなタイプの問題は発生しません。これが静的型付けの利点です。データの型を間違えたり、データの型を変換したりするプログラムはコンパイルされません。コードを実行する前に間違いを見つけることができます。
文字列と数値が混在するデータ ストリームがあるとします。最初に質問する必要があるのは、「それらをどうしたいのか、本当にそれらを混ぜ合わせる必要があるのか?」ということです。それらを分離するより良い方法はありますか?たとえば、Haskell では、完全な機能を備えた静的に型指定されたパーサーを作成するのは非常に簡単です (これが必要な場合は parsec を検索してください)。
新規: 非整数を無視する
(質問のタイトルを読み直すと、おそらくこれがあなたの求めているものです。)
非整数を無視するのは非常に簡単です。あなたが言及したように、このようにNothing
とその対応物を使用できますJust
myData = ["nan","45","3454.5","that's not an int, it's a float!","4","6"]
maybeInts :: [String] -> [Maybe Integer] -- type signature needed so Haskell knows you want Integers
maybeInts = map maybeRead
これは便利な関数maybeRead
を使用して各値を変換しますが、代わりにコードをコピーして貼り付ける必要がimport Network.CGI.Protocol (maybeRead)
ありData.Maybe
ます。
maybeRead :: Read a => String -> Maybe a
maybeRead = fmap fst . listToMaybe . reads
-- take all parses, grab the first one, ignore the remaining text
あなたが得る価値maybeInts myData
は
[Nothing, Just 45, Nothing, Nothing, Just 4, Just 6]
Nothing
しかし、おそらくあなたはそのすべて/Just
綿毛を望んでおらず、むしろ整数を取得したいだけです. フーグルするとモジュールから[Maybe a] -> [a]
見つかるので、定義しましょうcatMaybes
Data.Maybe
import Data.Maybe
intsOnly :: [String] -> [Int]
intsOnly = catMaybes.map maybeRead -- read them, ignore Nothings and unwrap Justs
だから、あなたがintsOnly myData
得るとき
[45,4,6]
そして、それらすべてに1つ追加したい場合は、次のことができます
map (+1) (intsOnly myData)
これは
[46,5,7]
要求に応じ。
整然とした型の混合
整数と文字列を混在させる必要がある場合は、次のことができます
[Left 5, Right "a", Right "hello", Left 6]
これはInt
egers またはString
s のいずれかのリストなので、タイプEither Int String
は ですが、数値とテキストだけでなく、整数、文字列、時間、または文字列のリストが必要な場合もあります。あなたは自分自身を転がすことができます:
data MyStuff = MyInt Int | MyString String | MyDate DateTime | MyList FilePath [String]
deriving Show
次のようなリストにすることができます
mystuff = [MyInt 6, MyString "Hello", MyString "MyString isn't a great name",
MyList "C:/temp/stuff.txt" ["yeah","give","them","problem-specific", "names"],
MyInt 12, MyString "instead"]
(Haskell では大文字と小文字が区別されることに注意してください。MyStuff
は型でMyString
あり、型コンストラクタ (データを型にラップする一種の関数) でmystuff
あり、変数です。すべての変数は定数です!)
あなたのデータはMy___
タグでどのようにラップされるのでしょうか? おそらく、ファイル内またはネットワーク接続などからのテキスト全体のロードとして開始され、パーサーはそれをint、文字列、日付、および文字列のリストに分割し、それに沿ってタグ付けしました(非常に簡単です)。
...そして、取得するデータに応じて定義が異なる関数を作成することで、それらを分離できます。
report :: MyStuff -> IO ()
report (MyInt n) = mapM_ print [1..n] -- turn the numbers 1..n into print commands, then combine them
report (MyString xs) = print xs -- just print the string.
report (MyList filename contents)
= writeFile filename (unlines contents) -- write them on seperate lines in the file
report _ = return () -- if it's anything else, ignore it and do nothing.
気が向いたらやってみようかな
mapM_ report mylist
そして、いくつかの数字、いくつかの文字列を出力し、ファイルを保存し、さらにいくつかの数字を出力し、最後に文字列を出力します。
2 つのタイプのいずれかになる値がある場合は、Either
その値のタイプを使用できます。typeまたはtype のいずれかである value もEither
同様に、パラメーターとして他の 2 つの型を取ります。2 つのケースは、2 つの異なるコンストラクターによって識別され、どちらかの左 ( ) または右 ( ) 型を意味します。あなたの場合、使用します。値を操作するには、パターン マッチングを使用できます。例えば、Either a b
a
b
Left
Right
a
b
Either String Integer
foo :: Either String Int -> Either String Int
foo (Left s) = Left s
foo (Right n) = Right (n+1)
質問で尋ねた変換を実装します。