Haskell プログラミング言語のあまり知られていないが便利な機能は何ですか。(言語自体はあまり知られていないことは理解していますが、私と一緒に作業します。1 行のコードでフィボナッチ数列を定義するなど、Haskell の単純なことの説明でさえ、私は支持します。)
- Haskellコアへの回答を制限してみてください
- 回答ごとに 1 つの機能
- ドキュメントへのリンクだけでなく、機能の例と簡単な説明を提供してください
- 最初の行として太字のタイトルを使用してフィーチャにラベルを付けます
Haskell プログラミング言語のあまり知られていないが便利な機能は何ですか。(言語自体はあまり知られていないことは理解していますが、私と一緒に作業します。1 行のコードでフィボナッチ数列を定義するなど、Haskell の単純なことの説明でさえ、私は支持します。)
ユーザー定義の制御構造
Haskell には省略形の三項演算子はありません。組み込みのif
- then
-は常に 3 項式であり、式です (命令型言語には=expression、=statementが含まelse
れる傾向があります)。ただし、必要に応じて、?:
if
True ? x = const x
False ? _ = id
(?)
三項演算子であると定義します。
(a ? b $ c) == (if a then b else c)
他のほとんどの言語では、独自の短絡論理演算子を定義するためにマクロを使用する必要がありますが、Haskell は完全に怠惰な言語であるため、問題なく機能します。
-- prints "I'm alive! :)"
main = True ? putStrLn "I'm alive! :)" $ error "I'm dead :("
私の脳はちょうど爆発した
このコードをコンパイルしようとすると:
{-# LANGUAGE ExistentialQuantification #-}
data Foo = forall a. Foo a
ignorefoo f = 1 where Foo a = f
次のエラー メッセージが表示されます。
$ ghc Foo.hs フー.hs:3:22: 私の脳はちょうど爆発しました。 存在量化されたコンストラクターのパターン バインディングを処理できません。 代わりに、case 式または do 表記を使用して、コンストラクターをアンパックします。 のバインディンググループで フーア パターンバインディング: Foo a = f `ignorefoo' の定義では: 無視foo f = 1 どこ フー a = f
自由定理
Phil Wadler は自由定理の概念を私たちに紹介し、それ以来、私たちは Haskell でそれらを悪用してきました。
Hindley-Milner スタイルの型システムのこれらのすばらしいアーティファクトは、パラメトリック性を使用して関数が実行しないことを伝えることにより、等式の推論に役立ちます。
たとえば、Functor のすべてのインスタンスが満たすべき 2 つの法則があります。
しかし、自由定理は、最初の定理をわざわざ証明する必要はないことを教えてくれますが、2 番目の定理を考えると、型シグネチャからだけで「自由」になります!
fmap :: Functor f => (a -> b) -> f a -> f b
怠惰には少し注意する必要がありますが、これは元の論文との存在下での自由定理に関するJanis Voigtlaender の最近の論文seq
で部分的にカバーされています。
共通リスト操作の省略形
以下は同等です。
concat $ map f list
concatMap f list
list >>= f
詳細を求められたので...
concat :: [[a]] -> [a]
concat
リストのリストを受け取り、それらを単一のリストに連結します。
map :: (a -> b) -> [a] -> [b]
map
関数をリストにマップします。
concatMap :: (a -> [b]) -> [a] -> [b]
concatMap
と同等です(.) concat . map
: リストに関数をマップし、結果を連結します。
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
AMonad
にはバインド操作があり>>=
、Haskell (またはそれにdo
相当するもの) で呼び出されます。リスト、別名[]
は ですMonad
。上記を代入[]
するm
と:
instance Monad [] where
(>>=) :: [a] -> (a -> [b]) -> [b]
return :: a -> [a]
Monad
リストに対して操作を行うのに自然なことは何ですか? モナド則を満たさなければなりません。
return a >>= f == f a
ma >>= (\a -> return a) == ma
(ma >>= f) >>= g == ma >>= (\a -> f a >>= g)
実装を使用すると、これらの法則が成立することを確認できます
instance Monad [] where
(>>=) = concatMap
return = (:[])
return a >>= f == [a] >>= f == concatMap f [a] == f a
ma >>= (\a -> return a) == concatMap (\a -> [a]) ma == ma
(ma >>= f) >>= g == concatMap g (concatMap f ma) == concatMap (concatMap g . f) ma == ma >>= (\a -> f a >>= g)
実際、これは の動作ですMonad []
。デモンストレーションとして、
double x = [x,x]
main = do
print $ map double [1,2,3]
-- [[1,1],[2,2],[3,3]]
print . concat $ map double [1,2,3]
-- [1,1,2,2,3,3]
print $ concatMap double [1,2,3]
-- [1,1,2,2,3,3]
print $ [1,2,3] >>= double
-- [1,1,2,2,3,3]
一般化された代数的データ型。型システムですべてのケースをカバーできるインタプリタの例を次に示します。
{-# LANGUAGE GADTs #-}
module Exp
where
data Exp a where
Num :: (Num a) => a -> Exp a
Bool :: Bool -> Exp Bool
Plus :: (Num a) => Exp a -> Exp a -> Exp a
If :: Exp Bool -> Exp a -> Exp a -> Exp a
Lt :: (Num a, Ord a) => Exp a -> Exp a -> Exp Bool
Lam :: (a -> Exp b) -> Exp (a -> b) -- higher order abstract syntax
App :: Exp (a -> b) -> Exp a -> Exp b
-- deriving (Show) -- failse
eval :: Exp a -> a
eval (Num n) = n
eval (Bool b) = b
eval (Plus e1 e2) = eval e1 + eval e2
eval (If p t f) = eval $ if eval p then t else f
eval (Lt e1 e2) = eval e1 < eval e2
eval (Lam body) = \x -> eval $ body x
eval (App f a) = eval f $ eval a
instance Eq a => Eq (Exp a) where
e1 == e2 = eval e1 == eval e2
instance Show (Exp a) where
show e = "<exp>" -- very weak show instance
instance (Num a) => Num (Exp a) where
fromInteger = Num
(+) = Plus
ネスト可能な複数行のコメント。
{- inside a comment,
{- inside another comment, -}
still commented! -}
トップレベル バインディングのパターン
five :: Int
Just five = Just 5
a, b, c :: Char
[a,b,c] = "abc"
なんてクールだ!その電話を時々保存しfromJust
ますhead
。
オプションのレイアウト
空白 (別名レイアウト) の代わりに明示的な中括弧とセミコロンを使用して、ブロックを区切ることができます。
let {
x = 40;
y = 2
} in
x + y
...または同等に...
let { x = 40; y = 2 } in x + y
... それ以外の ...
let x = 40
y = 2
in x + y
レイアウトが不要なため、Haskell プログラムは他のプログラムで簡単に作成できます。
オペレーターの固執
infix、infixl、または infixrキーワードを使用して、演算子の結合性と優先順位を定義できます。リファレンスからの例:
main = print (1 +++ 2 *** 3)
infixr 6 +++
infixr 7 ***,///
(+++) :: Int -> Int -> Int
a +++ b = a + 2*b
(***) :: Int -> Int -> Int
a *** b = a - 4*b
(///) :: Int -> Int -> Int
a /// b = 2*a - 3*b
Output: -19
中置記号の後の数字 (0 から 9) を使用すると、演算子の優先順位を定義できます。9 が最も強力です。Infix は結合性がないことを意味しますが、infixl は左に結合し、infixr は右に結合します。
これにより、複雑な演算子を定義して、単純な式として記述された高レベルの操作を実行できます。
バッククォートの間に配置すると、二項関数を演算子として使用することもできることに注意してください。
main = print (a `foo` b)
foo :: Int -> Int -> Int
foo a b = a + b
そのため、それらの優先順位を定義することもできます:
infixr 4 `foo`
seq
何かが底ではないことを確認するのに十分なだけ($!)
評価します。
次のプログラムは、「そこ」のみを出力します。
main = print "hi " `seq` print "there"
Haskell に慣れていない方のために説明すると、Haskell は一般的に厳密ではありません。つまり、関数の引数は必要な場合にのみ評価されます。
たとえば、次の例では「無視されました」と出力され、正常に終了します。
main = foo (error "explode!")
where foo _ = print "ignored"
seq
最初の引数が下の場合、下に評価することによってその動作を変更することが知られています。
例えば:
main = error "first" `seq` print "impossible to print"
... または同等に、中置記号なし ...
main = seq (error "first") (print "impossible to print")
...「最初」のエラーで爆発します。「印刷不可」と表示されることはありません。
したがって、厳密であるにもかかわらず、seq
積極的な言語が評価する方法で何かを評価しないことは少し驚くかもしれません。特に、次のプログラムではすべての正の整数を強制しようとはしません。代わりに、それが[1..]
底ではないことを確認し (これはすぐに見つかります)、「完了」と出力して終了します。
main = [1..] `seq` print "done"
C スタイルの列挙
トップレベルのパターン マッチングと算術シーケンスを組み合わせることで、連続する値を簡単に定義できます。
foo : bar : baz : _ = [100 ..] -- foo = 100, bar = 101, baz = 102
括弧を避ける
および関数には非常に便利な固定機能が(.)
あり、多くの場所で括弧を避けることができます。以下は同等です。($)
Prelude
f (g (h x))
f $ g $ h x
f . g $ h x
f . g . h $ x
flip
も役立ちます。以下は同等です。
map (\a -> {- some long expression -}) list
flip map list $ \a ->
{- some long expression -}
かわいい警備員
Prelude
を定義しotherwise = True
、完全なガード条件を非常に自然に読み取らせます。
fac n
| n < 1 = 1
| otherwise = n * fac (n-1)
読み取り可能な関数構成
Prelude
(.)
数学関数の合成であると定義します。つまり、g . f
最初に適用されf
、次にg
結果に適用されます。
の場合import Control.Arrow
、以下は同等です。
g . f
f >>> g
Control.Arrow
を提供します。instance Arrow (->)
これは、関数適用を逆方向に読みたくない人に便利です。
let 5 = 6 in ...
有効な Haskell です。
無限リスト
フィボナッチについて言及したので、次のような無限リストからフィボナッチ数を生成する非常にエレガントな方法があります。
fib@(1:tfib) = 1 : 1 : [ a+b | (a,b) <- zip fib tfib ]
@ 演算子を使用すると、パターン全体を fib として参照しながら、1:tfib 構造でパターン マッチングを使用できます。
内包リストは無限再帰に入り、無限リストを生成することに注意してください。ただし、有限の量を要求する限り、そこから要素を要求したり、それらを操作したりできます。
take 10 fib
リクエストする前に、すべての要素に操作を適用することもできます。
take 10 (map (\x -> x+1) fib)
これは Haskell のパラメーターとリストの遅延評価のおかげです。
モジュールのインポートとエクスポートの柔軟な仕様
インポートとエクスポートは素晴らしいです。
module Foo (module Bar, blah) -- this is module Foo, export everything that Bar expored, plus blah
import qualified Some.Long.Name as Short
import Some.Long.Name (name) -- can import multiple times, with different options
import Baz hiding (blah) -- import everything from Baz, except something named 'blah'
等式推論
Haskellは、純粋関数型であるため、等号を実際の等号として読み取ることができます(重複しないパターンがない場合)。
これにより、定義をコードに直接置き換えることができ、最適化の観点から、何かがいつ発生するかについてコンパイラーに多くの余裕が与えられます。
この形式の推論の良い例はここにあります:
http://www.haskell.org/pipermail/haskell-cafe/2009-March/058603.html
これは、インスタンスの有効なメンバーに期待される法則またはルールプラグマの形でもうまく現れます。たとえば、モナド法則です。
多くの場合、モナディックコードを単純化するために使用できます。
リストや高階関数を探しているなら、それはすでにそこにあります
標準ライブラリには、非常に多くの便利な高階関数があります。
-- factorial can be written, using the strict HOF foldl':
fac n = Data.List.foldl' (*) 1 [1..n]
-- there's a shortcut for that:
fac n = product [1..n]
-- and it can even be written pointfree:
fac = product . enumFromTo 1
怠惰
ユビキタスな怠惰とは、定義するようなことができることを意味します
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
しかし、構文と推論の点で、より多くの微妙な利点も提供します。
たとえば、厳密さのために、MLは値の制限に対処する必要があり、循環letバインディングを追跡するのに非常に注意が必要ですが、Haskellでは、すべてのletを再帰的にすることができ、とを区別する必要はありませval
んfun
。これにより、言語から主要な構文上の疣贅が削除されます。
これは間接的に私たちの素敵なwhere
節を生み出します。なぜなら、使用されるかもしれないし使用されないかもしれない計算をメインの制御フローから安全に移動し、怠惰に結果の共有を処理させることができるからです。
()を取り、値を返す必要のあるMLスタイルの関数の(ほぼ)すべてを、値の怠惰な計算だけで置き換えることができます。CAFでスペースが漏れないようにするために、時々そうすることを避ける理由がありますが、そのような場合はまれです。
最後に、無制限のeta-reductionを許可\x -> f x
します(fに置き換えることができます)。これにより、パーサーコンビネーターなどのコンビネーター指向プログラミングは、厳密な言語で同様の構造を操作するよりもはるかに快適になります。
これは、ポイントフリースタイルのプログラムについて推論するとき、またはプログラムをポイントフリースタイルに書き直すときに役立ち、引数のノイズを減らします。
並列リスト内包表記
(GHC特集)
fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]
列挙
Enumのインスタンスである型は、数値だけでなく、算術シーケンスで使用できます。
alphabet :: String
alphabet = ['A' .. 'Z']
独自のデータ型を含めて、Enum から派生させてデフォルトの実装を取得します。
data MyEnum = A | B | C deriving(Eq, Show, Enum)
main = do
print $ [A ..] -- prints "[A,B,C]"
print $ map fromEnum [A ..] -- prints "[0,1,2]"
モナド
それらはそれほど隠されているわけではありませんが、あなたがそれらを考えていない場所(リスト、Maybe-Types)であっても、どこにでもあります...