Монада для понимания с неявной монадой не получается, использовать наследование? - PullRequest
3 голосов
/ 20 июня 2019

Я сталкиваюсь с этим знаменитым 10-летним билетом в Scala https://github.com/scala/bug/issues/2823

Потому что я ожидаю, что for-понимания будут работать, как do-block в Haskell.И почему они не должны, Монады отлично сочетаются с сахаром?На данный момент у меня есть что-то вроде этого:

import scalaz.{Monad, Traverse}
import scalaz.std.either._
import scalaz.std.list._

type ErrorM[A] = Either[String, A]
def toIntSafe(s : String) : ErrorM[Int] = {
   try {
      Right(s.toInt)
   } catch {
      case e: Exception => Left(e.getMessage)
   }
}

def convert(args: List[String])(implicit m: Monad[ErrorM], tr: Traverse[List]): ErrorM[List[Int]] = {
   val expanded = for {
      arg <- args
      result <- toIntSafe(arg)
   } yield result

   tr.sequence(expanded)(m)
}

println(convert(List("1", "2", "3")))
println(convert(List("1", "foo")))

И я получаю ошибку

«Карта значений не является членом ErrorM [Int]» result <-toIntSafe (arg) </p>

Как мне вернуться к красивым, монадическим пониманиям, к которым я привык?Некоторые исследования показывают, что абстрактный класс FilterMonadic[A, Repr] - это то, что нужно расширять, если вы хотите быть пониманием, какие-нибудь примеры объединения FilterMonadic со скалязом?

  1. Можно ли повторно использовать мою монаду неявно ине нужно переопределять карту, flatMap и т. д.?

  2. Могу ли я сохранить свой псевдоним типа и не оборачивать его? Или хуже, переопределить классы дел для ErrorM?

Использование Scala 2.11.8

РЕДАКТИРОВАТЬ: я добавляю код на Haskell, чтобы показать, что он действительно работает в GHC без явных преобразователей монад, только обходы и экземпляры монад по умолчанию для Either и List.

type ErrorM = Either String

toIntSafe :: Read a => String -> ErrorM a
toIntSafe s = case reads s of
                  [(val, "")] -> Right val
                  _           -> Left $ "Cannot convert to int " ++ s

convert :: [String] -> ErrorM [Int]
convert = sequence . conv
    where conv s = do
            arg <- s
            return . toIntSafe $ arg

main :: IO ()
main = do
    putStrLn . show . convert $ ["1", "2", "3"]
    putStrLn . show . convert $ ["1", "foo"]

1 Ответ

2 голосов
/ 25 июня 2019

Ваш код haskell и ваш код scala не эквивалентны:

do
  arg <- s
  return . toIntSafe $ arg

соответствует

for {
      arg <- args
    } yield toIntSafe(arg)

Который отлично компилируется.

Чтобы понять, почему ваш пример не компилируется, мы можем его отключить:

for {
      arg <- args
      result <- toIntSafe(arg)
   } yield result

=

args.flatMap { arg =>
  toIntSafe(arg).map {result => result}
}

Сейчас смотрим на типы:

args: List[String]
args.flatMap: (String => List[B]) => List[B]
arg => toIntSafe(arg).map {result => result} : String => ErrorM[Int]

Что показывает проблему. flatMap ожидает функцию, возвращающую List, но вы даете ему функцию, возвращающую ErrorM.

код на Haskell в соответствии с:

do
    arg <- s
    result <- toIntSafe arg
    return result

не скомпилируется ни по одной и той же причине: пытается связать две разные монады, List и Either.

A для понимания в scala will или выражение do в haskell будет работать только для одной и той же монады, лежащей в основе, потому что они оба в основном являются синтаксическими переводами в серии flatMap s и >>= s соответственно. И те еще должны проверить тип.

Если вы хотите составить монады, то вы можете использовать монадные преобразователи (EitherT), хотя в вышеприведенном примере я не думаю, что вы хотите этого, поскольку вы действительно хотите упорядочить в конце.

Наконец, на мой взгляд, самый элегантный способ выразить ваш код:

def convert(args: List[String]) = args.traverse(toIntSafe)

Поскольку map, за которым следует sequence, составляет traverse

...