私はこのようなことをするのが悪いかどうか知りたいです:
data Alignment = LeftAl | CenterAl | RightAl
type Delimiter = Char
type Width = Int
setW :: Width -> Alignment -> Delimiter -> String -> String
このようなものではなく:
setW :: Int -> Char -> Char -> String -> String
これらの型を作り直すことは、より明確なコードと引き換えに数行しか使わないことを知っています。ただし、複数の関数にタイプDelimiter
を使用すると、このモジュールをインポートしたり、後でコードを読んだりする人にとって、これははるかに明確になります。
私はHaskellに比較的慣れていないので、この種のものの良い習慣は何なのかわかりません。これが良い考えではない場合、またはより明確に改善できるものが好ましい場合、それは何でしょうか?
タイプエイリアスを使用していますが、コードの読みやすさはわずかです。ただし、型の安全性を高めるには、newtype
ではなくtype
を使用することをお勧めします。このような:
data Alignment = LeftAl | CenterAl | RightAl
newtype Delimiter = Delimiter { unDelimiter :: Char }
newtype Width = Width { unWidth :: Int }
setW :: Width -> Alignment -> Delimiter -> String -> String
newtype
の追加のラッピングとアンラッピングを扱います。しかし、コードはさらなるリファクタリングに対してより堅牢になります。 このスタイルガイド は、ポリモーフィック型を特殊化する場合にのみtype
を使用することをお勧めします。
私はその悪い形は考慮しませんが、明らかに、Haskellコミュニティ全体については話しません。言語機能は、私が知る限り、その特定の目的のために存在します。コードを読みやすくすることです。
さまざまな「コア」ライブラリでの型エイリアスの使用例を見つけることができます。たとえば、Read
クラスはこのメソッドを定義します。
readList :: ReadS [a]
ReadS
型は単なる型エイリアスです
type ReadS a = String -> [(a, String)]
別の例は Forest
type in Data.Tree
です:
type Forest a = [Tree a]
Shershが指摘するように、newtype
宣言で新しい型をラップすることもできます。これは、元の型をなんらかの方法で制約する必要がある場合(たとえば、 スマートコンストラクター を使用する場合)またはOrphanインスタンスを作成せずに型に機能を追加する場合(通常、QuickCheckを定義することです) Arbitrary
インスタンスを、他の方法ではそのようなインスタンスに付属しないタイプに変換します)。
newtype
を使用すると、基になる型と同じ表現で新しい型が作成されますが、置換できません—good形式と見なされます。これは primitive obsession を避けるための安価な方法であり、Haskellでは関数の引数の名前がシグネチャに表示されないため、Haskellにとって特に便利です。
Newtypesは、便利なtypeclassインスタンスをぶら下げる場所にもなります。
Haskellのnewtypeは至る所に存在するため、時間の経過とともに言語はそれらを操作するためのいくつかのツールとイディオムを獲得しています。
Coercible newtypeコンストラクターがスコープ内にある場合に、newtypesとその基になる型の間の変換を簡略化する「魔法の」型クラス。関数の実装でボイラープレートを回避するのによく役立ちます。
ghci> coerce (Sum (5::Int)) :: Int
ghci> coerce [Sum (5::Int)] :: [Int]
ghci> coerce ((+) :: Int -> Int -> Int) :: Identity Int -> Identity Int -> Identity Int
ala
。 foldMap
などの関数で使用する可能性のある新しいタイプの選択を簡単にするイディオム(さまざまなパッケージで実装)。
ala Sum foldMap [1,2,3,4 :: Int] :: Int
GeneralizedNewtypeDeriving
。基本タイプで使用可能なインスタンスに基づいて、新しいタイプのインスタンスを自動派生させるための拡張。
DerivingVia
より一般的な拡張機能で、他のいくつかで使用可能なインスタンスに基づいて、新しいタイプのインスタンスを自動導出します同じ基本型を持つnewtype。
注意すべき重要な点の1つは、Alignment
とChar
の違いは、明快さの問題だけでなく、正確さの問題です。あなたのAlignment
型は、Char
が持っている多くの住民とは対照的に、有効な配置が3つしかないという事実を表します。これを使用することで、無効な値や操作の問題を回避し、警告がオンになっている場合にGHCが不完全なパターン一致について情報を提供できるようにします。
同義語については、意見が異なります。個人的には、type
のような小さな型のInt
の同義語は、厳密に同じものの異なる名前を追跡させることにより、認知負荷を高めることができると感じています。つまり、 leftaroundaboutは重要なポイントになります この種の同義語は、ソリューションのプロトタイピングの初期段階で、具体的な表現の詳細について必ずしも気にしたくない場合に役立ちます。ドメインオブジェクトに採用します。
(ここでのtype
に関する注釈は、主にnewtype
には当てはまりません。ただし、ユースケースは異なります。ただし、type
は、同じこと、newtype
はフィアットによって別のことを紹介します。これは驚くほど強力な動きになる可能性があります-詳細については danidiazの回答 を参照してください。)
間違いなく良いです。ここに別の例を示します。このデータ型にいくつかのopがあるとします。
data Form = Square Int | Rectangle Int Int | EqTriangle Int
perimeter :: Form -> Int
perimeter (Square s) = s * 4
perimeter (Rectangle b h) = (b * h) * 2
perimeter (EqTriangle s) = s * 3
area :: Form -> Int
area (Square s) = s ^ 2
area (Rectangle b h) = (b * h)
area (EqTriangle s) = (s ^ 2) `div` 2
次に、円を追加するとします。
data Form = Square Int | Rectangle Int Int | EqTriangle Int | Cicle Int
その操作を追加します。
perimeter (Cicle r ) = pi * 2 * r
area (Cicle r) = pi * r ^ 2
あまり良くないですね。 Floatを使用したい... FloatのすべてのIntを変更する必要があります
data Form = Square Double | Rectangle Double Double | EqTriangle Double | Cicle Double
area :: Form -> Double
perimeter :: Form -> Double
しかし、明確にするために、そして再利用のために、タイプを使用するとどうなるでしょうか。
data Form = Square Side | Rectangle Side Side | EqTriangle Side | Cicle Radius
type Distance = Int
type Side = Distance
type Radius = Distance
type Area = Distance
perimeter :: Form -> Distance
perimeter (Square s) = s * 4
perimeter (Rectangle b h) = (b * h) * 2
perimeter (EqTriangle s) = s * 3
perimeter (Cicle r ) = pi * 2 * r
area :: Form -> Area
area (Square s) = s * s
area (Rectangle b h) = (b * h)
area (EqTriangle s) = (s * 2) / 2
area (Cicle r) = pi * r * r
これにより、コードの1行だけを変更してタイプを変更できます。DistanceをIntにしたい場合は、それだけを変更します
perimeter :: Form -> Distance
...
totalDistance :: [Form] -> Distance
totalDistance = foldr (\x rs -> perimeter x + rs) 0
距離をフロートにしたいので、変更します。
type Distance = Float
Intに変更したい場合は、関数をいくつか調整する必要がありますが、それは他の問題です。