我正在學習 Haskell 并編造一些例子。我不確定為什么第二個示例不起作用
foo :: Int -> Int -> Maybe Int
foo 0 0 = Nothing
foo a b = Just $ a b
bar :: Int -> Maybe Int
bar 0 = Nothing
bar a = Just $ a 1
-- This works
Just 4 >>= bar
-- Why this doesn't work?
(Just 4 Just 4) >>= foo
-- This works
do
a <- Just 3
b <- Just 4
foo a b
uj5u.com熱心網友回復:
正如評論所說,當它只需要一個時,(Just 4 Just 4)嘗試將建構式應用于 3 個引數。Just所以,我會假設你想要類似的東西(Just 4, Just 4),并希望它像你的最后一個例子一樣作業。
“系結”運算子的型別是(>>=) :: Monad m => m a -> (m a -> b) -> m b. 這意味著在運算子之后預期的函式只接受一個引數,而不是兩個。所以,再一次,它不起作用的最終原因是,你的函式接受了錯誤數量的引數。(部分應用意味著您不必一次提供所有引數,但聽起來您希望其他一些資料被神奇地路由到缺少的引數......)
將您的do示例脫糖>>=轉化為:
Just 3 >>= \a -> Just 4 >>= \b -> foo a b
為了更清楚一點,我將括號括起來:
Just 3 >>= ( \a -> Just 4 >>= (\b -> foo a b) )
這使您更容易看到您可以簡化內部 lambda:
Just 3 >>= ( \a -> Just 4 >>= foo a )
所以,畢竟有可能將丟失的資料路由到額外的引數!但是,您必須自己制定路由...
Haskell 函式沒有什么特別神奇的地方。與動態語言相比,它們往往更注重如何呼叫它們。這里最大的“魔力”是型別檢查器通常可以判斷您何時沒有正確使用它們。
而且(正如其他答案所指出的)沒有什么神奇的>>=——它只是另一個功能,為了了解如何使用它,您需要查看它的型別。
uj5u.com熱心網友回復:
它不起作用,因為它>>=是一個完全正常的運算子(并且運算子是完全正常的函式)。
您似乎將其>>=視為一種特殊語法,用于從其左側的一元值中獲取值并將其提供給右側的函式。它不是特殊的語法;相反>>=,它本身是一個函式,它應用于其左側和右側的值(然后按照您的預期計算結果)。
但是,這意味著左右引數必須是可以作為普通值存在的事物的有效運算式;var = <expr>你可以簡單地用語法系結到變數的東西。Just 4 >>= bar之所以有效,是因為(除其他要求外)Just 4它本身是 type 的有效運算式,Maybe Int并且bar是 type 的有效運算式Int -> Maybe Int。Just 4 Just 4 >>= foo不起作用,因為不是Just 4 Just 4正確的運算式(它的型別是什么?);它被解釋為適用于 3 個單獨的引數、和,而您希望它是兩個單獨的值和。但是,即使您可以讓編譯器將那里的某些內容解釋為兩個單獨的值,也沒有辦法Just4Just4Just 4Just 4>>=將兩個單獨的值作為其左引數傳遞;它期望(在這種用法中) type 的單個值Just Int。
如果您有一個foo需要兩個引數的函式,并且您想從單子背景關系中的值中獲取這些引數,那么您不能只應用>>=您需要撰寫執行此操作的代碼(就像您最后一個帶有do塊的示例;還有許多其他方法可以做等效的事情)。
uj5u.com熱心網友回復:
其他答案描述了為什么這不起作用。但是 IMO 你想要這個是很合理的,而且確實Just 3 >>= \x -> Just 4 >>= \y -> foo x y是這個任務的一個愚蠢的解決方案。基本上,xand的y值是相互獨立的,但是您是按順序獲取它們的,其方式是完整的y計算原則上可能取決于x.
單子在這里并不是真正正確的抽象,它們太強大了。要獲取x和y非順序,您可以使用Applicative介面。現在大多數Haskellers更喜歡的形式(我認為)是
foo <$> Just 3 <*> Just 4
您可以將其解讀為“將有效值壓縮到具有兩個值Just 3的Just 4單個操作中,然后應用foo這些值”。
...實際上這并不是它的作業原理,而且對我來說,當我第一次了解應用程式時,這非常令人困惑。即,上面的運算式實際上被決議為
(foo <$> Just 3) <*> Just 4
看起來又像是順序式的。但事實并非如此,這里發生的只是一個柯里化/懶惰技巧,通過應用值傳遞多個值,而不必將它們分組到一個合適的元組中。像我解釋的那樣作業的代碼將是
uncurry foo <$> ((,)<$>Just 3<*>Just 4)
在這里,(,)<$>Just 3<*>Just 4計算為Just (3,4)。然后 fmappingfoo需要以非咖喱形式完成,因此這兩個引數被接受為一個元組。它在結構上很清晰,但很尷尬,因為我們正在反對 Haskell 的咖喱風格。
(在數學上,這個元組是概念上發生的事情:一般來說,你在一個單曲面類別中作業。應用函子的一些其他化身具有這樣的元組組合器作為它們的底層介面,而不是<*>; 例如>*<來自invertible包。)
訣竅foo<$>Just 3<*>Just 4在于,我們不是構建元組,而是從部分應用foo到3結果開始。這實際上還不需要任何應用程式/單子 - 我們基本上只是將包含的值轉換 - 通常:值s - 從3到foo 3,而不涉及它們的背景關系。你可以認為這是一個純粹的符號操作。請注意,型別是Maybe (Int -> Int)在這一點上。
然后使用<*>組合器將兩個背景關系壓縮Maybe在一起,同時將foo 3部分評估的函式應用于其第二個引數。
我個人很喜歡這種形式,也是等價的:
liftA2 foo (Just 3) (Just 4)
不過,我們還沒有完成:以上所有建議都給出了 type 的結果Maybe (Maybe Int)。要將其展平為Maybe Int,這就是您實際需要 monad 介面的地方。一種選擇是
join $ foo <$> Just 3 <*> Just 4
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/482692.html
標籤:哈斯克尔
