Неправильное упражнение на монадическую композицию

Фон

Я читаю в основном Адекватное руководство по функциональному программированию и делаю все упражнения. Я нахожусь в главе 9, Монадический лук, но мне сложно выполнять упражнения.

Упражнение 1

Рассматривая объект User следующим образом, используйте safeProp и map/join или chain, чтобы безопасно получить название улицы при задании пользователю:

// safeProp :: String -> Object -> Maybe a
const safeProp = curry((p, obj) => compose(Maybe.of, prop(p))(obj));

const user = {  
  id: 1,  
  name: 'Albert',  
  address: {  
    street: {  
      number: 22,  
      name: 'Walnut St',  
    },  
  },  
};

Решение 1

// getStreetName :: User -> Maybe String
const getStreetName = compose(
    chain( safeProp( "name" ) ),
    chain( safeProp( "street" ) ),
    safeProp( "address" )    
);

Это было легко. safeProp возвращает монаду Maybe. Итак, при составлении safeProp нам нужно использовать chain (он же flatMap) для продолжения, иначе вместо получения Maybe.of("value") мы получим Maybe.of( Maybe.of("value") ).

Выведенное правило: если вы хотите скомпоновать функцию A и функцию B, и обе возвращают монады, используйте chain!

Упражнение 2.

Учитывая следующие функции, используйте getFile для получения пути к файлу, удалите каталог и оставьте только базовое имя, а затем просто запишите его. Подсказка: вы можете использовать split и last для получения базового имени из пути к файлу.

// getFile :: () -> IO String
const getFile = () => IO.of('/home/mostly-adequate/ch9.md');

// pureLog :: String -> IO ()
const pureLog = str => new IO(() => console.log(str));

Решение 2

const getBaseName = compose( last, split("/") );

const logFilename = compose(
    chain( pureLog ),
    map( getBaseName ),
    getFile
);

Это немного сложнее, но я тоже справился.

Итак, getFile возвращает монаду ввода-вывода. Но getBaseMap возвращает только строку. Итак, у меня есть функция A, которая возвращает монаду, и функция B, которая возвращает примитивный тип. Я не могу составить их с помощью chain, потому что функция B не имеет ничего, что нужно сглаживать. Это означает, что мне нужно map, чтобы составить A и B!

Еще одно правило!

Теперь мне нужно составить B с помощью pureLog (C). После применения карты к B он вернет монаду ввода-вывода с преобразованным значением. Назовем это МБ. Учитывая, что мне нужно составить MB с помощью C (который возвращает монаду), я не могу применить правило 1 и просто использовать chain.

Фух!

Пойдем к последнему!

Упражнение 3:

Учитывая следующие функции, используйте validateEmail, addToMailingList и emailBlast, чтобы создать функцию, которая добавляет новое электронное письмо в список рассылки, если он действителен, а затем уведомляет весь список.

// validateEmail :: Email -> Either String Email // addToMailingList :: Email -> IO ([Email]) // emailBlast :: [Email] -> IO ()

Решение 3?

Понятия не имею, как это сделать ....

Вот что я сделал до сих пор:

// joinMailingList :: Email -> Either String (IO ())
const joinMailingList = compose(
    chain( emailBlast ),
    chain( addToMailingList ),
    validateEmail    
);

Но это неправильно. Я получаю следующую ошибку:

Функция имеет недопустимый тип; подсказка: joinMailingList должен возвращать Either String (IO ())

Вопросов:

  1. Как я могу это исправить? Может кто-нибудь объяснить мне, что не так?
  2. Должен ли я вывести дополнительные правила из предыдущих упражнений (не пропущено ли здесь какое-то правило компоновки)?

person Flame_Phoenix    schedule 19.05.2018    source источник
comment
Если вы проголосовали против, объясните, почему   -  person Flame_Phoenix    schedule 19.05.2018
comment
Можете ли вы сократить это до актуальной информации? Мне пришлось пролистать два экрана с материалами, прежде чем перейти к вашему актуальному вопросу / проблеме.   -  person Oliver Charlesworth    schedule 19.05.2018
comment
Ваше первое правило не совсем правильное: если вы хотите составлять действия (функции, возвращающие монаду), вам нужна композиция kleisli. chain предназначен для связывания эффекта (результата монадического вычисления) с другим действием.   -  person    schedule 19.05.2018
comment
@OliverCharlesworth: Нет, я не могу, потому что все это связано со вторым вопросом. Речь идет не только о том, чтобы выяснить, что не так, это еще и о том, чтобы выяснить, какие знания я упустил, чтобы ошибиться с первого раза.   -  person Flame_Phoenix    schedule 19.05.2018
comment
@ftor Я еще не изучал состав клеиси, думаю, это будет в следующих главах. В конце концов, я все еще учусь!   -  person Flame_Phoenix    schedule 19.05.2018


Ответы (1)


// validateEmail :: Email -> Either String Email
// addToMailingList :: Email -> IO([Email])
// emailBlast :: [Email] -> IO ()

означают, что вы не можете использовать Either.chain, вам нужно map над Either String, которое возвращает первая функция в композиции (validateEmail). Как вы знаете, «Я не могу составить их с помощью цепочки, потому что функция B не имеет ничего, что нужно сглаживать.».

Также обратите внимание, что у нас здесь две монады, две разные монады: Either и IO. chain не работает с любой монадой, он работает только с одним и тем же монадическим типом в обоих своих аргументах. Каждая монада (т.е. каждый тип, являющийся монадой) имеет свой собственный chain метод. Использование одной функции chain - это просто абстракция, которая использует полиморфизм времени выполнения (или полиморфизм времени компиляции, если в языке есть компилятор, поддерживающий это). Значит нам понадобится

function joinMailingList(email) {
    return Either.map(validateEmail(email), addAndBlast)
}
function addAndBlast(email) {
    return IO.chain(addToMailingList(email), emailBlast)
}
person Bergi    schedule 19.05.2018
comment
compose, похоже, выражает композицию слева направо , хотя. - person ; 19.05.2018
comment
@ftor Вы имеете в виду, что это настоящая ошибка? Может быть, я не думаю, что это важно, это довольно тривиально решить. - person Bergi; 19.05.2018