web-dev-qa-db-ja.com

Haskellの隠された機能

Haskellプログラミング言語のあまり知られていないが便利な機能は何ですか? (言語自体はあまり知られていないことは理解していますが、私と一緒に作業します。1行のコードでフィボナッチ数列を定義するなどのHaskellの簡単な説明でさえ、私は賛成します。)

  • Haskellコアへの回答を制限してみてください
  • 回答ごとに1つの機能
  • ドキュメントへのリンクだけでなく、機能の例と簡単な説明を提供します
  • 最初の行として太字のタイトルを使用して機能にラベルを付けます
32
Claudiu

私の脳は爆発したばかりです

このコードをコンパイルしようとすると:

{-# 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 
25
ephemient

ユーザー定義の制御構造

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 :("
40
ephemient

フーグル

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をチェックしてください!

28
yairchu

自由定理

Phil Wadlerが私たちに 無料の定理 の概念を紹介し、それ以来、Haskellでそれらを悪用してきました。

Hindley-Milner型システムのこれらの素晴らしいアーティファクトは、パラメトリシティを使用して関数が実行しないことを通知することにより、方程式の推論に役立ちます。

たとえば、Functorのすべてのインスタンスが満たす必要がある2つの法則があります。

  1. forall fg。 fmapf。 fmap g = fmap(f。g)
  2. fmap id = id

しかし、無料の定理は、最初の定理をわざわざ証明する必要はないことを示していますが、2番目の定理を考えると、型の署名だけで「無料」になります。

fmap :: Functor f => (a -> b) -> f a -> f b

怠惰には少し注意する必要がありますが、これは元の論文と、seqの存在下での自由定理に関するJanisVoigtlaenderの より最近の論文 で部分的にカバーされています。

22
Edward KMETT

一般的なリスト操作の省略形

以下は同等です。

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]
20
ephemient

一般化された代数的データ型。型システムですべてのケースをカバーできるインタプリタの例を次に示します。

{-# 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
19
Norman Ramsey

ネスト可能な複数行コメント

{- inside a comment,
     {- inside another comment, -}
still commented! -}
19
ephemient

トップレベルバインディングのパターン

five :: Int
Just five = Just 5

a, b, c :: Char
[a,b,c] = "abc"

なんてクールなんだ!時々fromJustheadへの呼び出しを保存します。

18
Martijn

オプションのレイアウト

ブロックを区切るために、空白(別名レイアウト)の代わりに明示的な中括弧とセミコロンを使用できます。

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プログラムは他のプログラムで簡単に作成できます。

17
Jonathan Tran

オペレーターの固定性

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`
16

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"
16
Jonathan Tran

Cスタイルの列挙

トップレベルのパターンマッチングと等差数列を組み合わせると、連続する値を定義する便利な方法が得られます。

foo : bar : baz : _ = [100 ..]    -- foo = 100, bar = 101, baz = 102
15
beerboy

括弧を避ける

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 -}
15
ephemient

かなりの警備員

Preludeotherwise = Trueを定義し、完全なガード条件を非常に自然に読み取らせます。

fac n
  | n < 1     = 1
  | otherwise = n * fac (n-1)
15
ephemient

読み取り可能な関数構成

Preludeは、_(.)_を数学関数の合成として定義します。つまり、_g . f_は最初にfを適用し、次にgを結果に適用します。

_import Control.Arrow_の場合、以下は同等です。

_g . f
f >>> g
_

_Control.Arrow_はinstance Arrow (->)を提供します。これは、関数適用を逆方向に読みたくない人に最適です。

13
ephemient

let 5 = 6 in ...は有効なHaskellです。

11
Martijn

無限リスト

あなたがフィボナッチについて言及したので、次のような無限のリストから フィボナッチ数を生成する の非常にエレガントな方法があります:

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によるパラメーターとリストの遅延評価のおかげです。

10

モジュールのインポートとエクスポートの柔軟な仕様

インポートとエクスポートは素晴らしいです。

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'
9
ephemient

方程式の推論

Haskellは、純粋に機能的であるため、等号を実際の等号として読み取ることができます(重複しないパターンがない場合)。

これにより、定義をコードに直接置き換えることができ、最適化の観点から、問題がいつ発生するかについてコンパイラーに多くの余裕が与えられます。

この形式の推論の良い例はここにあります:

http://www.haskell.org/pipermail/haskell-cafe/2009-March/058603.html

これは、インスタンスの有効なメンバーに期待される法則またはルールプラグマの形でもうまく現れます。たとえば、モナド法則です。

  1. a >> = f == faを返します
  2. m >> =リターン== m
  3. (m >> = f)>> = g == m >> =(\ x-> f x >> = g)

多くの場合、モナドコードを単純化するために使用できます。

8
Edward KMETT

リストまたは高階関数を探している場合は、すでにそこにあります

標準ライブラリには非常に多くの便利で高階関数があります。

-- 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
8
ephemient

強化されたパターンマッチング

  • 怠惰なパターン
  • 反駁できないパターン

    let ~(Just x) = someExpression
    

パターンマッチング を参照してください

6
Dario

並列リスト内包表記

(特別なGHC機能)

  fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]
6
Dario

怠惰

ユビキタスな怠惰とは、定義するようなことができることを意味します

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

しかし、構文と推論の点で、より多くの微妙な利点も提供します。

たとえば、厳密さのために、MLは 値の制限 を処理する必要があり、循環letバインディングを追跡するのに非常に注意が必要ですが、Haskellでは、すべてのletを再帰的にすることができ、区別する必要はありません。 valおよびfun。これにより、主要な構文上の疣贅が言語から削除されます。

これにより、素敵なwhere句が間接的に発生します。これは、使用される場合と使用されない場合がある計算をメインの制御フローから安全に移動し、結果の共有を怠惰に処理できるためです。

()を取り、値を返す必要があるMLスタイル関数の(ほぼ)すべてを、値の遅延計算だけで置き換えることができます。 CAFs でスペースがリークするのを避けるために、時々そうすることを避ける理由がありますが、そのようなケースはまれです。

最後に、無制限のeta-reduction(\x -> f xはf)に置き換えることができます。これにより、パーサーコンビネーターなどのコンビネーター指向プログラミングは、厳密な言語で同様の構造を操作するよりもはるかに快適になります。

これは、ポイントフリースタイルのプログラムについて推論するとき、またはプログラムをポイントフリースタイルに書き直すときに役立ち、引数のノイズを減らします。

6
Edward KMETT

列挙

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]"
5
beerboy

モナド

それらはそれほど隠されていませんが、あなたがそれらについて考えていない場所(リスト、多分-タイプ)でさえ、単にどこにでもあります...

3
Dario