2013-04-04 2 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] 

Am Я в состоянии выполнить эту задачу, например, если пользователь «Боб» хочет стать поклонником фильма «Мама Миа!» то база данных будет обновлять список поклонников из:

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

к:

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

Заранее спасибо всем, предоставленных ответов !!

+8

Вы пытались написать эту функцию? Как это выглядело? Что случилось? – Floris

+0

Должно ли это быть 'статьFan :: Title -> Fan -> Database -> Database' или' статьFan :: Fan -> Title -> Database -> Database'? После определения всех этих синонимов типов стыдно не использовать их ... – dave4420

+0

Ваше право, это будет лучший тип для использования функции и сделать ее более ясной. – user2240649

ответ

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

+0

Что? Мне жаль, что я не следую. Я подытожил, что должны быть 'fans' и' filter_db'. Если вы можете получить эти рабочие, тогда вы все настроены – jozefg

+2

«Особенностью», я думаю, что jozefg означает, что вы не сказали нам, пытаетесь ли вы написать функцию, как она выглядела и какие проблемы у вас возникли , См. Первый комментарий Флориса к вашему вопросу. Нам легче помочь кому-то, если мы знаем, в какой части задачи они испытывают трудности. – mhwombat

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 

Который просто итерация по списку фильмов в базе данных и сделать наше обновление тогда и только тогда Флят заголовки тот, который мы пытаемся изменить. Обратите внимание на @ -syntax, который позволяет нам снимать фильм, который мы рассматриваем «в целом», и все еще деконструируем его.

Проблемы с этим методом несметны, хотя-это просто очень сложно! У нас есть ряд базовых предположений, связанных с тем, как мы реализуем becomeFan, который может стать desynched с другими функциями, которые мы пишем. К счастью, 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 с-мы не можем иметь несколько матчей). Недостатком здесь является то, что мы можем desync Title в ключах Database с Title в самом 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 v и Set.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, чтобы сделать наш доступ к Map, Set и Film еще проще.

Чтобы сделать это, мы будем импортировать модуль

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, что мы и хотели.

Тогда мы можем использовать At экземпляр Map «s, чтобы создать нашу функцию переделки, в значительной степени, как и раньше, но записанный в виде строки операций линзы

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 

Хотя, мы могли бы сделать то же самое с Control.Monad.State.withState использованием любого определения becomeFan.

Смежные вопросы