Haskellプログラミング言語のあまり知られていないが便利な機能は何ですか? (言語自体はあまり知られていないことは理解していますが、私と一緒に作業します。1行のコードでフィボナッチ数列を定義するなどのHaskellの簡単な説明でさえ、私は賛成します。)
私の脳は爆発したばかりです
このコードをコンパイルしようとすると:
{-# LANGUAGE ExistentialQuantification #-}
data Foo = forall a. Foo a
ignorefoo f = 1 where Foo a = f
次のエラーメッセージが表示されます。
$ ghc Foo.hs Foo.hs:3:22: 脳が爆発した。 存在記号コンストラクターのパターンバインディングを処理できない。 代わりに、case-expressionまたはdo-notationを使用して、コンストラクターを解凍します。 for Foo a のバインディンググループ内パターンバインディング内:Foo a = f `ignorefoo 'の定義: ignorefoo f = 1 where Foo a = f
ユーザー定義の制御構造
Haskellには速記の三項演算子はありません。組み込みのif
-then
-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 :("
フーグル
Hoogleはあなたの友達です。確かに、それは「コア」の一部ではないので、cabal install hoogle
これで、「高階関数を探している場合、それはすでに存在している」( ephemientのコメント )方法がわかりました。しかし、どのようにしてその機能を見つけますか?フーグル付き!
$ hoogle "Num a => [a] -> a"
Prelude product :: Num a => [a] -> a
Prelude sum :: Num a => [a] -> a
$ hoogle "[Maybe a] -> [a]"
Data.Maybe catMaybes :: [Maybe a] -> [a]
$ hoogle "Monad m => [m a] -> m [a]"
Prelude sequence :: Monad m => [m a] -> m [a]
$ hoogle "[a] -> [b] -> (a -> b -> c) -> [c]"
Prelude zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
Hoogle-googleプログラマーは、コンピューターを使用する場合と同じように、自分でプログラムを紙に書くことはできません。しかし、彼とマシンは一緒になって、考慮されないように強制されています*。
ところで、フーグルが好きなら、必ずhlintをチェックしてください!
自由定理
Phil Wadlerが私たちに 無料の定理 の概念を紹介し、それ以来、Haskellでそれらを悪用してきました。
Hindley-Milner型システムのこれらの素晴らしいアーティファクトは、パラメトリシティを使用して関数が実行しないことを通知することにより、方程式の推論に役立ちます。
たとえば、Functorのすべてのインスタンスが満たす必要がある2つの法則があります。
しかし、無料の定理は、最初の定理をわざわざ証明する必要はないことを示していますが、2番目の定理を考えると、型の署名だけで「無料」になります。
fmap :: Functor f => (a -> b) -> f a -> f b
怠惰には少し注意する必要がありますが、これは元の論文と、seq
の存在下での自由定理に関するJanisVoigtlaenderの より最近の論文 で部分的にカバーされています。
一般的なリスト操作の省略形
以下は同等です。
concat $ map f list
concatMap f list
list >>= f
詳細が要求されたので...
concat :: [[a]] -> [a]
concat
はリストのリストを取得し、それらを1つのリストに連結します。
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
Monad
にはbind操作があり、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が最も強くなります。中置は結合性がないことを意味しますが、中置は左に関連付け、中置は右に関連付けます。
これにより、複雑な演算子を定義して、単純な式として記述された高レベルの操作を実行できます。
バイナリ関数をバッククォートの間に配置すると、演算子として使用することもできることに注意してください。
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
は、最初の引数がbottomの場合、bottomに評価することで、その動作を変更することが知られています。
例えば:
main = error "first" `seq` print "impossible to print"
...または同等に、中置なしで.。
main = seq (error "first") (print "impossible to print")
...「最初」のエラーで爆発します。 「印刷不可能」とは決して印刷されません。
したがって、seq
が厳密であっても、熱心な言語が評価する方法で何かを評価しないのは少し驚くかもしれません。特に、次のプログラムですべての正の整数を強制しようとはしません。代わりに、[1..]
が一番下ではありません(すぐに見つかります)。「done」と出力して終了します。
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
並列リスト内包表記
(特別なGHC機能)
fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]
怠惰
ユビキタスな怠惰とは、定義するようなことができることを意味します
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
しかし、構文と推論の点で、より多くの微妙な利点も提供します。
たとえば、厳密さのために、MLは 値の制限 を処理する必要があり、循環letバインディングを追跡するのに非常に注意が必要ですが、Haskellでは、すべてのletを再帰的にすることができ、区別する必要はありません。 val
およびfun
。これにより、主要な構文上の疣贅が言語から削除されます。
これにより、素敵なwhere
句が間接的に発生します。これは、使用される場合と使用されない場合がある計算をメインの制御フローから安全に移動し、結果の共有を怠惰に処理できるためです。
()を取り、値を返す必要があるMLスタイル関数の(ほぼ)すべてを、値の遅延計算だけで置き換えることができます。 CAFs でスペースがリークするのを避けるために、時々そうすることを避ける理由がありますが、そのようなケースはまれです。
最後に、無制限のeta-reduction(\x -> f x
はf)に置き換えることができます。これにより、パーサーコンビネーターなどのコンビネーター指向プログラミングは、厳密な言語で同様の構造を操作するよりもはるかに快適になります。
これは、ポイントフリースタイルのプログラムについて推論するとき、またはプログラムをポイントフリースタイルに書き直すときに役立ち、引数のノイズを減らします。
列挙
Enumのインスタンスである任意の型は、数値だけでなく、等差数列で使用できます。
alphabet :: String
alphabet = ['A' .. 'Z']
独自のデータ型を含めて、列挙型から派生してデフォルトの実装を取得します。
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]"
モナド
それらはそれほど隠されていませんが、あなたがそれらについて考えていない場所(リスト、多分-タイプ)でさえ、単にどこにでもあります...