Быстрое функциональное программирование - "Необязательное связывание" и "Дополнительная карта"
Я работаю над Функциональным программированием в Swift, и у меня действительно нет хорошего способа понять различия в концепции, представленной в Раздел опций.
Образец при работе с опциями имеет тенденцию:
if let thing = optionalThing {
return doThing(thing)
}
else {
return nil
}
Эта идиома выполняется лаконично со стандартной библиотечной функцией map
map(optionalThing) { thing in doThing(thing) }
Затем книга продолжает и вводит понятие необязательного связывания, в котором моя способность дифференцироваться начинает разрушаться.
В книге описывается функция map
:
func map<T, U>(optional: T?, f: T -> U) -> U?
{
if let x = optional {
return f(x)
}
else {
return nil
}
}
И также помогает нам определить необязательную функцию привязки. Примечание: в книге используется оператор >>=
, но я решил использовать именованную функцию, потому что она помогает мне увидеть сходство.
func optionalBind<T, U>(optional: T?, f: T -> U?) -> U?
{
if let x = optional {
return f(x)
}
else {
return nil
}
}
Реализация для обоих этих методов выглядит идентично мне. Единственное различие между ними - это аргумент функции, который они принимают:
-
map
принимает функцию, которая преобразует T в U
-
optionalBind
принимает функцию, которая преобразует T в необязательный U
Результат "гнездования" этих вызовов функций вредит моему мозгу:
func addOptionalsBind(optionalX: Int?, optionalY: Int?) -> Int?
{
return optionalBind(optionalX) { x in
optionalBind(optionalY) { y in
x + y
}
}
}
func addOptionalsMap(optionalX: Int?, optionalY: Int?) -> Int?
{
return map(optionalX) { x in
map(optionalY) { y in
x + y
}
}
}
- Функция
addOptionalsBind
выполняет именно то, что вы ожидаете от нее.
- Функция
addOptionalsMap
не компилируется:
'Int??' не конвертируется в 'Int?'
Я чувствую, что я близок к пониманию того, что происходит здесь (и необязательное целое снова завернуто в факультативное? Но как? Почему? Hu?), Но я также достаточно далеко от понимания того, что я не совсем уверен в умный вопрос.
Ответы
Ответ 1
То, что происходит, может быть более ясным с более подробной реализацией addOptionalsMap
. Начните с самого внутреннего вызова map
, вместо того, что у вас есть, вместо этого используйте это:
let mappedInternal: Int? = map(optionalY) { (y: Int) -> Int in
return x + y
}
Закрытие, предоставляемое map
, принимает Int
и возвращает и Int
, тогда как вызов map
сам возвращает необязательный параметр: Int?
. Никаких сюрпризов! Отпустите один шаг и посмотрите, что произойдет:
let mappedExternal: ??? = map(optionalX) { (x: ???) -> ??? in
let mappedInternal: Int? = map(optionalY) { (y: Int) -> Int in
return x + y
}
return mappedInternal
}
Здесь мы можем увидеть наше значение mappedInternal
сверху, но есть несколько типов left undefined. map
имеет подпись (T?, T -> U) -> U?
, поэтому нам нужно только выяснить, что T
и U
в этом случае. Мы знаем, что возвращаемое значение замыкания mappedInternal
равно Int?
, поэтому U
здесь становится Int?
. T
, с другой стороны, может оставаться не факультативным Int
. Подставляя, получаем следующее:
let mappedExternal: Int?? = map(optionalX) { (x: Int) -> Int? in
let mappedInternal: Int? = map(optionalY) { (y: Int) -> Int in
return x + y
}
return mappedInternal
}
Закрытие T -> U
, которое оценивается как Int -> Int?
, а целое выражение map
заканчивается отображением Int?
в Int??
. Не то, что вы имели в виду!
Контрастируйте это с помощью версии optionalBind
, полностью заданной по типу:
let boundExternal: ??? = optionalBind(optionalX) { (x: ???) -> ??? in
let boundInternal: Int? = optionalBind(optionalY) { (y: Int) -> Int? in
return x + y
}
return boundInternal
}
Посмотрите на те типы ???
для этой версии. Для optionalBind
нам нужно T -> U?
замыкание и иметь возвращаемое значение Int?
в boundInternal
. Таким образом, оба T
и U
в этом случае могут быть просто Int
, а наша реализация выглядит так:
let boundExternal: Int? = optionalBind(optionalX) { (x: Int) -> Int? in
let boundInternal: Int? = optionalBind(optionalY) { (y: Int) -> Int? in
return x + y
}
return boundInternal
}
Ваша путаница может возникнуть из-за того, что переменные могут быть "подняты" как дополнительные. Легко видеть при работе с одним слоем:
func optionalOpposite(num: Int?) -> Int? {
if let num = num {
return -num
}
return nil
}
optionalOpposite
можно вызвать либо с переменной типа Int?
, как это явно предполагает, либо не необязательной переменной типа Int
. В этом втором случае необязательная переменная неявно преобразуется в необязательную (то есть отмененную) во время вызова.
map(x: T, f: T -> U) -> U?
делает подъем в возвращаемом значении. Поскольку f
объявлен как T -> U
, он никогда не возвращает необязательный U?
. Но возвращаемое значение map
как U?
означает, что f(x)
возвращается к U?
при возврате.
В вашем примере внутреннее закрытие возвращает x + y
, a Int
, которое поднимается до Int?
. Это значение затем снова поднимается на Int??
, что приводит к несоответствию типа, поскольку вы объявили addOptionalsMap
для возврата Int?
.
Ответ 2
- отображает функцию, преобразующую T в U
- optionalBind принимает функцию, которая преобразует T в необязательный U
Совершенно верно. В этом вся разница. Рассмотрим действительно простую функцию, lift()
. Он преобразует T
в T?
. (В Haskell эта функция будет называться return
, но это слишком запутанно для программистов, не относящихся к Haskell, и, кроме того, return
- это ключевое слово).
func lift<T>(x: T) -> T? {
return x
}
println([1].map(lift)) // [Optional(1)]
Великий. Теперь, если мы снова это сделаем:
println([1].map(lift).map(lift)) // [Optional(Optional(1))]
Хммм. Итак, теперь у нас есть Int??
, и это боль, с которой приходится иметь дело. На самом деле мы бы предпочли только один уровень необязательности. Давайте построим функцию для этого. Мы будем называть его flatten
и сглаживать двойную опцию до одного необязательного.
func flatten<T>(x: T??) -> T? {
switch x {
case .Some(let x): return x
case .None : return nil
}
}
println([1].map(lift).map(lift).map(flatten)) // [Optional(1)]
Высокий. Только то, что мы хотели. Вы знаете, что .map(flatten)
случается очень много, поэтому дайте ему имя: flatMap
(это то, что называют такие языки, как Scala). Несколько минут игры должны доказать вам, что реализация flatMap()
является в точности реализацией bindOptional
, и они делают то же самое. Возьмите опцию и что-то, что возвращает необязательный, и получите только один уровень "необязательной" из него.
Это очень распространенная проблема. Это настолько распространено, что у Haskell есть встроенный оператор для него (>>=
). Это настолько распространено, что у Swift также есть встроенный оператор, если вы используете методы, а не функции. Он назывался факультативно-цепочкой (это настоящий позор, что Swift не распространяется на функции, но Swift любит методы намного больше, чем любит функции):
struct Name {
let first: String? = nil
let last: String? = nil
}
struct Person {
let name: Name? = nil
}
let p:Person? = Person(name: Name(first: "Bob", last: "Jones"))
println(p?.name?.first) // Optional("Bob"), not Optional(Optional(Optional("Bob")))
?.
действительно просто flatMap
(*), что на самом деле просто bindOptional
. Почему разные имена? Ну, оказывается, что "карта, а затем сглаженная" эквивалентна другой идее, называемой монадической связью, которая думает об этой проблеме по-другому. Да, монады и все такое. Если вы думаете о T?
в качестве монады (какой она есть), то flatMap
оказывается обязательной операцией связывания. (Так что "bind" - это более общий термин, применимый ко всем монадам, тогда как "плоская карта" относится к деталям реализации. Я считаю, что "плоская карта" легче научить людей, но YMMV.)
Если вы хотите еще более длинную версию этого обсуждения и как он может применяться к другим типам, чем Optional
, см. Flattenin 'Your Mappenin'.
(*) .?
также может выглядеть как map
в зависимости от того, что вы проходите. Если вы пройдете T->U?
, тогда он flatMap
. Если вы пройдете T->U
, тогда вы можете думать о нем как о map
, или вы все еще думаете, что это flatMap
, где U
неявно продвигается до U?
(что будет делать Swift автоматически).