質問
Haskellプログラミング言語のあまり知られていないが便利な機能は何ですか。 (言語自体はあまり知られていませんが、私と一緒に動作します。1行のコードでフィボナッチ数列を定義するなど、Haskellの簡単なことの説明でさえ、私は賛成します。)
- Haskellコアへの回答を制限してみてください
- 回答ごとに1つの機能
- ドキュメントへのリンクだけでなく、機能の例と簡単な説明を入力してください
- 最初の行として太字のタイトルを使用して機能にラベルを付けます
解決
私の脳が爆発しました
このコードをコンパイルしようとする場合:
{-# LANGUAGE ExistentialQuantification #-}
data Foo = forall a. Foo a
ignorefoo f = 1 where Foo a = f
次のエラーメッセージが表示されます。
$ ghc Foo.hs Foo.hs:3:22: My brain just exploded. I can't handle pattern bindings for existentially-quantified constructors. Instead, use a case-expression, or do-notation, to unpack the constructor. In the binding group for Foo a In a pattern binding: Foo a = f In the definition of `ignorefoo': ignorefoo f = 1 where Foo a = f
他のヒント
ユーザー定義の制御構造
Haskellには、簡単な三項演算子はありません。組み込みの if
- then
- else
は常に3進数であり、式です(命令型言語では?:
= expression、 if
= statement)。ただし、必要に応じて、
True ? x = const x
False ? _ = id
(?)
を三項演算子として定義します:
(a ? b $ c) == (if a then b else c)
他のほとんどの言語のマクロに頼って独自の短絡論理演算子を定義する必要がありますが、Haskellは完全に怠zyな言語なので、動作します。
-- prints "I'm alive! :)"
main = True ? putStrLn "I'm alive! :)" $ error "I'm dead :("
Hoogle
Hoogleはあなたの友達です。確かに、それは" core"の一部ではないので、 cabal install hoogle
これで、「高階関数を探しているなら、それはすでにそこにある」ということがわかりました。 (エフェミエントのコメント)。しかし、どのようにしてその機能を見つけますか? hoogleで!
$ 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プログラマーは、コンピューターの助けを借りて行うのと同じように、自分でプログラムを自分で紙に書くことはできません。しかし、彼と機械は一緒に考えられないように強制されています*。
ところで、hoogleが気に入ったら、必ずhlintをチェックしてください!
無料定理
Phil Wadlerは、自由定理の概念を紹介してくれました。 以来、Haskellで悪用されています。
Hindley-Milnerスタイルのタイプシステムのこれらの素晴らしいアーティファクトは、パラメトリック性を使用して 関数が何をしないのかを伝えることにより、等式推論に役立ちます。
たとえば、Functorのすべてのインスタンスが満たすべき2つの法則があります:
- forall f g。 fmap f。 fmap g = fmap(f。g)
- fmap id = id
しかし、無料の定理は、最初の1つをわざわざ証明する必要はないことを示していますが、2番目の場合、型シグネチャからだけ「無料」になります!
fmap :: Functor f => (a -> b) -> f a -> f b
怠lazに少し注意する必要がありますが、これについては元の論文と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
Monad
には bind 操作があり、これはHaskell(またはその糖衣 do <で
&gt;&gt; =
と呼ばれます) / code> -equivalent)。リスト(別名 []
)は 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]
ネスト可能な複数行コメント。
{- inside a comment,
{- inside another comment, -}
still commented! -}
一般化された代数データ型。型システムですべてのケースをカバーできるインタープリターの例を次に示します。
{-# 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
トップレベルバインディングのパターン
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プログラムは他のプログラムで簡単に作成できます。
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")
...は&quot; first&quot;でエラーを発生させます。 「印刷できない」と印刷されることはありません。
したがって、 seq
が厳密であっても、熱心な言語が評価する方法で何かを評価しないのは少し驚くかもしれません。特に、次のプログラムですべての正の整数を強制しようとはしません。代わりに、 [1 ..]
が最下部(すぐに見つかる)でないことを確認し、「完了」を出力して終了します。
main = [1..] `seq` print "done"
演算子の修正
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`
括弧の回避
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)
Cスタイルの列挙
トップレベルのパターンマッチングと算術シーケンスを組み合わせることで、連続した値を簡単に定義できます。
foo : bar : baz : _ = [100 ..] -- foo = 100, bar = 101, baz = 102
読みやすい関数構成
Prelude
は、(。)
を数学関数合成として定義します。つまり、 gです。 f
は最初に f
を適用し、次に g
を結果に適用します。
Control.Arrow
をインポートする場合、以下は同等です。
g . f
f >>> g
Control.Arrow
は instance Arrow(-&gt;)
を提供します。これは、関数アプリケーションを逆読みしたくない人にとっては便利です。
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'
リストまたは高階関数を探している場合は、すでにそこにあります
標準ライブラリには非常に多くの便利な高階関数があります。
-- 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
等式推論
Haskellは、純粋に機能的であるため、等号を実際の等号として読み取ることができます(重複しないパターンがない場合)。
これにより、コードに定義を直接代入することができ、最適化の観点から、コンパイラーに問題が発生する時期について多くの余裕が与えられます。
この形式の推論の良い例はここにあります:
http://www.haskell.org/pipermail /haskell-cafe/2009-March/058603.html
これは、インスタンスの有効なメンバーに期待される法律またはルールプラグマの形で、たとえばMonadの法則でうまく現れます:
- returrn a&gt;&gt; = f == f a
- m&gt;&gt; = return == m
- (m&gt;&gt; = f)&gt;&gt; = g == m&gt;&gt; =(\ x-&gt; f x&gt;&gt; = g)
モナドコードを簡素化するためによく使用できます。
怠azine
ユビキタスな怠inessは、定義などのことができることを意味します
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
しかし、それはまた、構文と推論の観点から、より多くの微妙な利点も提供します。
たとえば、厳密性のため、MLは値の制限、および循環letバインディングの追跡には非常に注意を払っていますが、Haskellでは、すべてのletを再帰的にすることができ、 val
と fun <を区別する必要はありません/ code>。これにより、言語から主要な構文上のいぼが削除されます。
これは間接的に素敵な where
句を生成します。これは、使用される可能性のある計算と使用されない計算をメイン制御フローから安全に移動でき、結果の共有を怠に任せることができるためです
値の遅延計算だけで、値を取得して返す必要があるMLスタイルの関数を(ほぼ)置き換えることができます。 CAF でスペースのリークを避けるために、時々そうすることを避ける理由がありますが、まれです。
最後に、無制限のイータ削減を許可します( \ x-&gt; 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)でもどこにでもあります...