2013-04-04 90 views
0

我想寫的功能是讓用戶輸入他們的名字和他們想成爲粉絲的電影。Haskell將元素添加到現有列表

這是當前的代碼我使用:

type Title = String 
type Actor = String 
type Cast = [Actor] 
type Year = Int 
type Fan = String 
type Fans = [Fan] 
type Period = (Year, Year) 
type Film = (Title, Cast, Year, Fans) 
type Database = [Film] 

testDatabase :: Database 
testDatabase = [("Casino Royale", ["Daniel Craig", "Eva Green", "Judi Dench"], 2006, ["Garry", "Dave", "Zoe", "Kevin", "Emma"]), 
    ("Cowboys & Aliens", ["Harrison Ford", "Daniel Craig", "Olivia Wilde"], 2011, ["Bill", "Jo", "Garry", "Kevin", "Olga", "Liz"]),  
     ("Catch Me If You Can", ["Leonardo DiCaprio", "Tom Hanks"], 2002, ["Zoe", "Heidi", "Jo", "Emma", "Liz", "Sam", "Olga", "Kevin", "Tim"]),   
      ("Mamma Mia!", ["Meryl Streep", "Pierce Brosnan"], 2008, ["Kevin", "Jo", "Liz", "Amy", "Sam", "Zoe"])] 

以此爲類型:

becomeFan :: String -> String -> [Film] -> [Film] 

我是否能執行此任務,例如,如果用戶「鮑勃」希望成爲電影「媽媽咪呀」的粉絲!

["Kevin", "Jo", "Liz", "Amy", "Sam", "Zoe"] 

到:那麼數據庫會從更新列表球迷提前

["Bob", "Kevin", "Jo", "Liz", "Amy", "Sam", "Zoe"] 

感謝所有提供的答案!

+8

你嘗試寫這樣的功能?它看起來像什麼?發生了什麼? – Floris 2013-04-04 13:42:05

+0

如果是這樣'becomeFan ::標題 - >風扇 - >數據庫 - > Database'或'becomeFan ::範 - >標題 - >數據庫 - > Database'?定義所有這些類型的同義詞之後,它是一個恥辱不使用它們...... – dave4420 2013-04-04 14:27:32

+0

您的權利,這將是一個更好的類型要使用的功能,使其更清晰 – user2240649 2013-04-04 14:30:21

回答

1

既然你還沒有真正顯示任何具體的,我只是提供一些一般的提示..

add :: String -> String -> Database -> Database 
add name film db = (film, cast, year, name : fans) : filteredDb 
    where (_, cast, year, fans) = get the movie 
     filtered_db = remove the entry about ${film} from the db 

希望這是足以讓你開始,讓我知道如果你有問題。

+0

有自己錯過了什麼細節可能需要的?並感謝您的回答非常讚賞:) – user2240649 2013-04-04 14:15:45

+0

什麼?對不起,我很抱歉。我總結了什麼'fans'和'filtered_db'必須是。如果你能得到那些然後工作你都設置 – jozefg 2013-04-04 14:21:54

+2

通過的「特效藥」,我認爲jozefg意味着你沒有告訴我們,如果你試着寫的功能,是什麼樣子,和你有什麼問題。請參閱弗洛里斯對您的問題的第一條評論。如果我們知道他們遇到困難的任務的哪一部分,幫助他人會更容易。 – mhwombat 2013-04-04 16:29:07

1

這是另一種解決方法。

becomeFan :: Title -> Fan -> Database -> Database 
becomeFan title newFan = map f where 
    f film @ (title', cast, year, fans) 
     | title == title' = --TODO 
     | otherwise  = --TODO 
6

這是因爲創建一個Endo一個整潔的例子,即功能a -> a喜歡你[Film] -> [Film]是一個無狀態的語言來處理「國家」的好方法。讓我們在潛水。


所以我們的目標是創建一個像becomeFan "Joseph" "7 1/2" :: [Film] -> [Film]一個功能是「電影數據庫更新功能」。要執行此更新,您需要修改電影數據庫以更新電影"7 1/2"的粉絲列表,使其包含"Joseph"。我們假設每個用戶的名字是全局唯一的,並且多次寫入這個函數。

現在我們假設,如果影片不在我們的數據庫中,那麼becomeFan不會做任何事情,並且數據庫不包含重複項。

首先我們有直接的遞歸版本。

becomeFan _ _ [] = [] -- empty film database 
becomeFan name film ([email protected](title, cast, year, fans) : fs) 
    | film == title = (title, cast, year, name:fans) : fs 
    | otherwise  = f : becomeFan name film fs 

這將只是重複倒在數據庫影片列表和使我們更新且僅當弗利姆標題匹配我們正在試圖修改的一個。請注意0​​-語法,它允許我們將電影作爲「整體」進行檢查,並仍然對其進行解構。

雖然這種方法面臨的挑戰無數,但它非常複雜!我們有許多基本假設與我們實施becomeFan的方式相關,這些假設可能會與我們編寫的其他函數一起被剝離。幸運的是,Haskell很好,很好解決這樣的問題。

第一步是引入一些更強大的數據類型。


我們要做的是消除一些類型的同義詞,並介紹一些更強大的容器類型,尤其是Set其行爲類似於數學集合和Map它就像一本字典或哈希。

import qualified Data.Set as Set 
import qualified Data.Map as Map 

我們還對Film使用「記錄」類型。記錄同構於(「功能等同於」)元組,但具有對文檔有用的命名字段,並讓我們使用更少的類型同義詞。

type Name = String 
type Year = Int 
data Film = Film { title :: Title, cast :: Set Name, year :: Year, fans :: Set Name) 

通過使用Map Title Film來代表我們的數據庫中,我們也得到保證電影的獨特性(一個Map使得Title鍵零個或一個Film S-我們不能有多個匹配)。這裏的缺點是我們可能會在Database密鑰中同步TitleTitle中的Film類型本身。

type Database = Map Title Film 

那麼如何在這個新系統中重寫becomeFan

becomeFan name title = 
    Map.alter update title where 
    update Nothing = Nothing -- that title was not in our database 
    update (Just f) = Just (f { fans = Set.insert name (fans f) }) 

現在我們扶着Map.alter :: (Maybe v -> Maybe v) -> k -> Map k v -> Map k vSet.insert :: a -> Set a -> Set a大多是做我們的繁重和維護各種唯一性約束。請注意,Map.alter的第一個參數是一個函數Maybe v -> Maybe v,它允許我們處理缺失的影片(如果輸入是Nothing)並決定從數據庫中刪除膠片(如果我們返回Nothing)。

這也是值得注意的是,我們的內部函數update :: Maybe Film -> Maybe Film可以更容易寫成fmap (\f = f { fans = Set.insert name (fans f) })解除「純粹」的更新步驟上放入Maybe,因爲它是一個Functor


我們可以做得更好嗎?當然,但這裏讓人感到困惑。在大多數情況下,以前的答案可能是你最好的選擇。但是,讓我們開玩笑吧。

我們可以使用Control.Lens的鏡片使我們更容易訪問到MapSetFilm

要做到這一點,我們將導入模塊

import Control.Lens 

並重寫Film類型,以便庫可以自動生成使用微距鏡頭。

data Film = Film { _title :: Title, _cast :: Set Name, _year :: Year, _fans :: Set Name } 
$(makeLenses ''Film) 

我們所要做的就是在前面加上一個下劃線到每個記錄字段名稱,Control.Lens.makeLenses會自動生成根據原先的名字我們的鏡頭。因此,在該行之後,我們有像title :: Lens' Film Title這樣的功能,這正是我們想要的功能。

然後我們可以使用MapAt實例來創建我們的改造功能,之前相當多的,但寫成鏡頭操作的字符串

becomeFan name film = over (at film) (fmap . over fans . Set.insert $ name) 

其中over (at film)推廣並取代Map.alter(fmap . over fans . Set.insert $ name)替代了我們update我們之前定義的內部函數。

我們甚至可以構建一個強大的二傳手鏡頭直接着眼於有一定Film粉絲列表中的某個風扇的存在。

isFan :: Name -> String -> Setter' Database Bool 
isFan name film = at film . mapped . fans . contains name 

這些方法在第一相當討厭的,並有很奇怪的(但完全可檢查的)類型,但他們變得非常漂亮,一旦你習慣了在抽象的那個水平工作。它「像英語一樣讀取」,感覺就像XPath的好處。

becomeFan name film = isFan name film .~ True 

,並用這種結構,我們甚至可以立即升級的全過程爲State單子。

flip execState initialDB $ do 
    isFan "Joseph" "7 1/2"  .= True 
    isFan "Steve" "Citizen Kane" .= True 
    -- oh, wait, nevermind 
    isFan "Joseph" "7 1/2"  .= False 

雖然,我們可以使用任何becomeFan定義做同樣的Control.Monad.State.withState