我在 Python 中創建了這個函式
def calc(a): return lambda op: {
' ': lambda b: calc(a b),
'-': lambda b: calc(a-b),
'=': a}[op]
所以你可以做這樣的計算
calc(1)(" ")(1)(" ")(10)("-")(7)("=")
結果將是 5
我想在 Haskell 中使用相同的函式來了解 lambdas——但是我遇到了決議錯誤:
我的代碼如下所示:
calc :: Int -> (String -> Int)
calc a = \ op
| op == " " = \ b calc a b
| op == "-" = \ b calc a b
| op == "=" = a
main = calc 1 " " 1 " " 10 "-" 7 "="
uj5u.com熱心網友回復:
您發布的代碼存在許多語法問題。不過,我不會在這里討論它們:在完成基本的 Haskell 教程后,您會自己發現它們。相反,我將專注于該專案的一個更基本的問題,即型別并沒有真正解決。然后我將展示一種不同的方法,它可以讓您獲得相同的結果,向您展示一旦您了解了更多,就可以在 Haskell 中實作。
雖然在 Python 中有時回傳一個 int 函式,有時回傳一個 int 是可以的,但這在 Haskell 中是不允許的。GHC 必須在編譯時知道將回傳什么型別;您不能在運行時根據字串是否存在來做出決定"="。因此,“保持calc”引數需要與“給我答案”引數不同的型別。
這在 Haskell 中是可能的,而且實際上是一種有很多應用程式的技術,但它可能不是初學者的最佳起點。你正在發明延續。您想calc 1 plus 1 plus 10 minus 7 equals為其中使用的名稱的某些定義生成 5。實作這一點需要 Haskell 語言的一些高級功能和一些有趣的型別1,這就是為什么我說它不適合初學者。但是,下面是一個實作這個目標的實作。我就不詳細解釋了,因為要先學的東西太多了。希望在對 Haskell 基礎知識進行一些研究后,您可以回到這個有趣的問題并理解我的解決方案。
calc :: a -> (a -> r) -> r
calc x k = k x
equals :: a -> a
equals = id
lift2 :: (a -> a -> a) -> a -> a -> (a -> r) -> r
lift2 f x y = calc (f x y)
plus :: Num a => a -> a -> (a -> r) -> r
plus = lift2 ( )
minus :: Num a => a -> a -> (a -> r) -> r
minus = lift2 (-)
ghci> calc 1 plus 1 plus 10 minus 7 equals
5
1當然calc 1 plus 1 plus 10 minus 7 equals看起來很像1 1 10 - 7,這很簡單。這里的重要區別在于,這些是中綴運算子,因此將其決議為(((1 1) 10) - 7),而您嘗試在 Python 中實作的版本和我的 Haskell 解決方案的決議方式類似((((((((calc 1) plus) 1) plus) 10) minus) 7) equals)- 沒有偷偷摸摸的中綴運算子,并且calc可以控制所有組合.
uj5u.com熱心網友回復:
每次呼叫Haskell 型別的函式時,它A -> B都必須回傳固定型別的值B(或者無法終止,或者拋出例外,但讓我們忽略這一點)。
Python 函式沒有類似的約束。回傳值可以是任何值,沒有型別限制。作為一個簡單的例子,考慮:
def foo(b):
if b:
return 42 # int
else:
return "hello" # str
在您發布的 Python 代碼中,您利用此功能使其calc(a)(op)成為函式(lambda)或整數。
在 Haskell 中,我們不能這樣做。這是為了確保代碼可以在編譯時進行型別檢查。如果我們寫
bar :: String -> Int
bar s = foo (reverse (reverse s) == s)
不能期望編譯器驗證引數的計算結果總是True- 通常是不可判定的。編譯器只要求 foo 的型別類似于Bool -> Int. 但是,我們不能將該型別分配給foo上面顯示的定義。
那么,我們實際上可以在 Haskell 中做什么?
一種選擇是濫用型別類。Haskell 中有一種方法可以利用某種復雜型別的類機制來創建一種“可變引數”函式。那會讓
calc 1 " " 1 " " 10 "-" 7 :: Int
型別檢查并評估所需的結果。我不是在嘗試這樣做:至少在我看來,這很復雜且“駭人聽聞”。這個hack是用printfHaskell實作的,讀起來不太好看。
另一種選擇是創建自定義資料型別并向呼叫語法添加一些中綴運算子。這也利用了 Haskell 的一些高級功能來進行所有型別的檢查。
{-# LANGUAGE GADTs, FunctionalDependencies, TypeFamilies, FlexibleInstances #-}
data R t where
I :: Int -> R String
F :: (Int -> Int) -> R Int
instance Show (R String) where
show (I i) = show i
type family Other a where
Other String = Int
Other Int = String
(#) :: R a -> a -> R (Other a)
I i # " " = F (i ) -- equivalent to F (\x -> i x)
I i # "-" = F (i-) -- equivalent to F (\x -> i - x)
F f # i = I (f i)
I _ # s = error $ "unsupported operator " s
main :: IO ()
main =
print (I 1 # " " # 1 # " " # 10 # "-" # 7)
最后一行5按預期列印。
關鍵思想是:
型別
R a表示中間結果,可以是整數或函式。如果它是一個整數,我們記住該行中的下一個內容應該是一個字串I i :: R String。如果它是一個函式,我們記得接下來應該是一個整數,因為有F (\x -> ...) :: R Int.運算子
(#)采用 type 的中間結果R a,下一個要處理的“事物”(int 或 string) typea,并在“其他型別”中產生一個值Other a。這里,Other a被定義為型別Int(分別String) whenaisString(resp.Int)。
uj5u.com熱心網友回復:
這是一個簡單的解決方案,與其他答案的高級解決方案相比,我更接近于您的 Python 解決方案。這不是慣用的解決方案,因為就像您的 Python 解決方案一樣,它將使用運行時失敗而不是編譯器中的型別。
所以,Python 的本質是這樣的:你要么回傳一個函式,要么回傳一個 int。在 Haskell 中,根據運行時值回傳不同型別是不可能的,但是可以回傳包含不同資料的型別,包括函式。
data CalcResult = ContinCalc (Int -> String -> CalcResult)
| FinalResult Int
calc :: Int -> String -> CalcResult
calc a " " = ContinCalc $ \b -> calc (a b)
calc a "-" = ContinCalc $ \b -> calc (a-b)
calc a "=" = FinalResult a
出于最后將變得清楚的原因,我實際上會提出以下變體,與典型的 Haskell 不同,它沒有咖喱:
calc :: (Int, String) -> CalcResult
calc (a," ") = ContinCalc $ \b op -> calc (a b,op)
calc (a,"-") = ContinCalc $ \b op -> calc (a-b,op)
calc (a,"=") = FinalResult a
現在,你不能只是在上面堆砌函式應用程式,因為結果永遠不僅僅是一個函式——它只能是一個包裝函式。因為應用比處理它們的函式更多的引數似乎是一個失敗案例,結果應該在Maybemonad 中。
contin :: CalcResult -> (Int, String) -> Maybe CalcResult
contin (ContinCalc f) (i,op) = Just $ f i op
contin (FinalResult _) _ = Nothing
為了列印最終結果,讓我們定義
printCalcRes :: Maybe CalcResult -> IO ()
printCalcRes (Just (FinalResult r)) = print r
printCalcRes (Just _) = fail "Calculation incomplete"
printCalcRes Nothing = fail "Applied too many arguments"
現在我們可以做
ghci> printCalcRes $ contin (calc (1," ")) (2,"=")
3
好的,但是對于更長的計算,這會變得非常尷尬。請注意,我們在兩次操作后都有 a,Maybe CalcResult因此我們不能contin再次使用。此外,需要向外匹配的括號也很麻煩。
幸運的是,Haskell 不是 Lisp 并且支持中綴運算子。并且因為我們無論如何都會得到Maybe結果,所以不妨在資料型別中包含失敗案例。
然后,完整的解決方案是這樣的:
data CalcResult = ContinCalc ((Int,String) -> CalcResult)
| FinalResult Int
| TooManyArguments
calc :: (Int, String) -> CalcResult
calc (a," ") = ContinCalc $ \(b,op) -> calc (a b,op)
calc (a,"-") = ContinCalc $ \(b,op) -> calc (a-b,op)
calc (a,"=") = FinalResult a
infixl 9 #
(#) :: CalcResult -> (Int, String) -> CalcResult
ContinCalc f # args = f args
_ # _ = TooManyArguments
printCalcRes :: CalcResult -> IO ()
printCalcRes (FinalResult r) = print r
printCalcRes (ContinCalc _) = fail "Calculation incomplete"
printCalcRes TooManyArguments = fail "Applied too many arguments"
這允許你寫
ghci> printCalcRes $ calc (1," ") # (2," ") # (3,"-") # (4,"=")
2
uj5u.com熱心網友回復:
chi 的回答說你可以用“復雜型別的類機器”來做到這一點,就像printf做的那樣。你可以這樣做:
{-# LANGUAGE ExtendedDefaultRules #-}
class CalcType r where
calc :: Integer -> String -> r
instance CalcType r => CalcType (Integer -> String -> r) where
calc a op
| op == " " = \ b -> calc (a b)
| op == "-" = \ b -> calc (a-b)
instance CalcType Integer where
calc a op
| op == "=" = a
result :: Integer
result = calc 1 " " 1 " " 10 "-" 7 "="
main :: IO ()
main = print result
這是相當脆弱的,非常不推薦用于生產用途,但無論如何它都可以作為(最終?)學習的東西。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/369103.html
上一篇:preg_replace阿拉伯標題,如?????-?????-??-???
下一篇:如何正確使用.count函式?
