노트 자습서를 읽지 마라

31720 단어 scalafunctional

모든 교과서가 다른 교과서보다 더 좋은 순환을 주장한다는 것을 깨기 위해서, 나는 정말 이 교과서가 너로 하여금 그 어떤 교과서보다 리스트를 더 잘 이해하게 하지 않을 것이라고 생각한다.이것은 읽기 강좌가 너를 이렇게 멀리 가게 할 수 밖에 없기 때문이다.반대로 너는 너 자신의 것을 쓰고 그것을 출판하려고 해야 한다.모든 세부 사항을 발굴하고 스스로 모든 것을 시도하며 독자가 피할 수 없는 모든 문제에 대답한다.나는 Dan Piponi's의 생각에서 영감을 얻었다. 나는 본래 스스로 명세서를 발명할 수 있었고, 명세서는 실제로는 멕시코 부침개가 아니라고 단언했다.
다음은 제가 2018년 9월에 쓴 교과서입니다. 작은 수정이 있어서 제가 리스트를 이해하는 데 도움을 주었습니다.

Brent Yorgey의 못생긴 문제


scala에서, 우리는 이렇게 방법을 한데 묶는 것을 정말 좋아한다.그것은 읽기와 디버깅이 쉽다.
List(1, 2, 3, 4, 5)
  .map(_ + 10)
  .filter(_ % 2 == 0)
  .take(2)
우리가 개발하고 있는 가상 프로그램은 일반적인 목록이 아니라 목록이 필요합니다. 이 함수들은 모든 함수에서 목록 결과와 디버깅 문자열을 되돌려줍니다.
object DebugList {
  //variable argument syntax so it works like List.apply
  def apply[A](a: A*):DebugList[A] = DebugList(List(a: _*))
}

case class DebugList[A](l: List[A]){
  def map[B](f: A => B): (DebugList[B], String) =
    (DebugList(l map f), "mapped")

  def filter(f: A => Boolean): (DebugList[A], String) =
    (DebugList(l filter f), "filtered")

  def take(n: Int): (DebugList[A], String) =
    (DebugList(l take n), s"took$n")
}
디버그 목록은 이렇게 사용됩니다...이거 너무 못생겼어.
val (dl1, str1) = DebugList(1, 2, 3, 4, 5).map(_ + 10)
val (dl2, str2) = dl1.filter(_ % 2 == 0)
val (dl3, str3) = dl2.take(2)
그러나 이것이 바로 우리의 응용 프로그램에 필요한 것이기 때문에 우리는 그것을 일하게 하려고 노력할 것이다.
문제는 우리가 반드시 원조에 대해 이 서투른 일치를 해야 한다는 것이다.또한 디버그 문자열을 순서대로 보려면 다음과 같이 해야 합니다.s"$str1 $str2 $str3"읽거나 인쇄하거나 쓰다.원조가 문제이기 때문에, 우리는 원조를 다른 종류에 두어 보자. 이렇게 하면 우리는 flatMap 같은 함수를 작성하여 우리를 위해 대략적인 일을 할 수 있다.

평면도와 지도


이 케이스 클래스 flatMapmap 의 정의를 채울 때, 생성된 디버깅 대상은 이 디버깅 대상과 f의 결과의 내용을 포함하는 문자열을 가지고 있어야 합니다.
case class Debug[A](a: A, s: String) {
  def flatMap[B](f: A => Debug[B]): Debug[B] = {
    val Debug(b, s1) = f(a)
    Debug(b, s + s1)
  }

  def map[B](f: A => B): Debug[B] =
    Debug(f(a), s)
}
다음에 우리는 수반 대상에서 구조 함수를 사용해야 한다. 이 구조 함수는 일반적인 구A 대상을 Debug[A] 대상으로 전환하는 방법을 제공한다.

들어가는 길


우리가 단위 함수를 채울 때, 빈 문자열을 사용할 수 없을 때, 빈 문자열은 합리적인 선택이다.
case object Debug {
  def apply[A](x: A): Debug[A] =
    unit(x)

  def unit[A](x: A): Debug[A] =
    Debug(x, "")
}

사용자 정의 목록


현재 우리는 그 모듈을 재구성했습니다. 모듈이 아닌 새로운 디버깅 형식으로 더 좋은 debuglist를 만들 수 있습니다.
object BetterDebugList {
  //variable argument syntax so it works like List.apply
  def apply[A](a: A*): BetterDebugList[A] = BetterDebugList(List(a: _*))
}

case class BetterDebugList[A](l: List[A]){
  def map[B](f: A => B): Debug[BetterDebugList[B]] =
    Debug(BetterDebugList(l map f), "mapped")

  def filter(f: A => Boolean): Debug[BetterDebugList[A]] =
    Debug(BetterDebugList(l filter f), "filtered")

  def take(n: Int): Debug[BetterDebugList[A]] =
    Debug(BetterDebugList(l take n), s"took $n")
}
이제 BetterDebugList를 사용할 수 있습니다.
val debug = Debug(BetterDebugList(1, 2, 3, 4, 5))
  .flatMap(_.map(_ + 10))
  .flatMap(_.filter(_ % 2 == 0))
  .flatMap(_.take(2))
와!이것은 우리가 처음 사용한 것 같다List.더 많은 원조가 일치하지 않습니다!
그 현식 flatMap 호출을 숨기기 위해서, 우리는 이해할 수 있다. 왜냐하면 그들은 더욱 아름답지만, 하는 일은 완전히 같기 때문이다.
val debug = for {
  w <- Debug(BetterDebugList(1, 2, 3, 4, 5))
  x <- w.map(_ + 10)
  y <- x.filter(_ % 2 == 0)
  z <- y.take(2)
} yield z
중간 상태마다 이름을 지정하는 것에 싫증이 나면 이해하기 위해 같은 이름을 지정할 수 있다.그것은 거의 우리의 불변 코드를 읽기에 약간 가변적인 것 같다.
val debug = for {
  x <- Debug(BetterDebugList(1, 2, 3, 4, 5))
  x <- x.map(_ + 10)
  x <- x.filter(_ % 2 == 0)
  x <- x.take(2)
} yield x
이렇게 하면 디버그 문자열을 얻을 수 있습니다.
println(debug.s)

서프라이즈너는 영수증을 하나 만들었다.


많은 다른 함수식 프로그래밍 도구와 같이monad는 합법적이고 유용한 코드를 사용합니다. 그렇지 않으면 사용하기 어렵고 더욱 자연스러울 수 있습니다.우리가 원하는 다른 종류의 디버깅 가능한 버전을 만들기 위해 이 Debug 클래스를 어떻게 사용하는지 주의하십시오.
Scala에서 우리는 항상 명세서를 사용하는데, 왜냐하면 그것들은 매우 자연스럽기 때문이다.ListOption는 모두 리스트입니다. 우리는 거의 모든 Scala 초보자 강좌에서 볼 수 있습니다.

사진 출처:

네, 그런데 명세서가 뭐예요?


영수증 필요...
1 - 평면도(바인딩이라고도 함)
2-셀(일반적으로 Scala에서 apply를 사용하여 수행)
3 - 세 개의 단자 법칙을 따르다
여기에 네가 이미 익숙한 리스트의 예가 있다.그것들은 모두 apply 방법을 사용하는데,'unit'라는 함수가 아니라, 그것들은 모두 flatMap 방법을 사용한다.
List(1)                    == List.unit(1)
List(1,2,3)                == List.unit(1,2,3)
Option(5)                  == Option.unit(5)
Try(throw new Exception()) == Try.unit(throw new Exception())
단자가 되기 위해서도 세 개의 단자 법칙을 따라야 한다.이러한 법칙은 우리가 예상한 방식에 따라 코드를 재구성하고 예측할 수 있는 결과를 얻을 수 있도록 확보할 뿐이다.

단자 법칙


f와 g는 함수입니다.
m는 리스트의 실례로'리스트 동작'이라고도 부른다
1 - 오른쪽 ID
unit(z).flatMap(f) == f(z)
2-왼쪽 표식
m.flatMap(unit) == m
3 - 결합성
m.flatMap(f).flatMap(g) == m.flatMap(x => f(x).flatMap(g))
이 법칙들이 정의한 예들을 단자 목록으로 봅시다.만약 이 진술들이 항상 정확하지 않다면 List를 사용하는 것이 얼마나 이상한지 상상해 보세요.
1 - 오른쪽 ID
List(2).flatMap(x => List(x * 5)) == List(2 * 5)
2-왼쪽 표식
List(2).flatMap(List(_)) == List(2)
3 - 결합성
List(2).flatMap(w => List(w, w)).flatMap(y => List(y * 2)) == 
List(2).flatMap(x => List(x, x).flatMap(z => List(z * 2)))

단자 의 법칙 을 깨다


FMCounter 클래스는 호출된 횟수를 집계합니다flatMap.그것은 명세서처럼 보이지만, 세 가지 법칙 중 일부를 위반했다.
이것은 그것의 정의다.어떤 법률을 위반했는지 봅시다.
case object FMCounter {
  def unit[A](a: A): FMCounter[A] =
    FMCounter(a, 0)

  def append(str: String, end: String): FMCounter[String] =
    unit(str + end)
}

case class FMCounter[A](a: A, counter: Int) {
  def flatMap[B](f: A => FMCounter[B]): FMCounter[B] = {
    val FMCounter(b, k) = f(a)
    FMCounter(b, counter + k + 1)
  }

  def map[B](f: A => B): FMCounter[B] =
    FMCounter(f(a), counter)
}
FMCounter는 정확한 신분을 파괴했다.다음은 반례입니다.
FMCounter.unit("My").flatMap(x => FMCounter.unit(x + "Counter")) = FMCounter(MyCounter,1)
FMCounter.unit("My" + "Counter") = FMCounter(MyCounter,0) 
// not the same!
FMCounter가 왼쪽 표식을 파괴합니다.다음은 반례입니다.
FMCounter.unit("MyCounter").flatMap(FMCounter.unit) = FMCounter(MyCounter,1)
FMCounter.unit("MyCounter") = FMCounter(MyCounter,0) 
// not the same!
하지만 FMCounter는 사실상 관련이 있다.
연관성의 정의를 보면, 모든 면에서 평면을 호출하는 횟수가 똑같다. 이것은 그것이 통과되었다는 것을 잘 보여준다.
그러나 만약 당신이 더 공식적인 것을 찾고 싶다면, 여기에 비전통적인 증명이 하나 있는데, 그것은 scala-ish 문법을 사용했다.만약 이것이 당신의 잼이 아니라면, 언제든지 과거를 되돌아보세요.
let {FM} be the set of all monadic actions of type FMCounter
let f : A => FMCounter[B]
let g : B => FMCounter[C]

Theorem:  ∀ x ∈ {FM} x.flatMap(f).flatMap(g) == x.flatMap(a => f(a).flatMap(g))
          x                               = FMCounter[A](a: A, i:         Int)
          f(a)                            = FMCounter[B](b: B, k0:        Int)
          x.flatMap(f)                    = FMCounter[B](b: B, i+k0+1:    Int)
          g(b)                            = FMCounter[C](c: C, k1:        Int)
          x.flatMap(f).flatMap(g)         = FMCounter[C](c: C, k0+k1+2:   Int)

          h: A => FMCounter[C]            = (a: A) => f(a).flatMap(g)        
          h                               = (a: A) => {
                                                f(a) = FMCounter[B](b: B, k0:      Int)
                                                g(b) = FMCounter[C](b: C, k1:      Int)
                                                       FMCounter[C](b: C, k0+k1+1: Int)
                                            } 

          h(a)                            = FMCounter[C](b: C, k0+k1+1: Int)
          x.flatMap(a => f(a).flatMap(g)) = x.flatMap(a => h(a))
          x.flatMap(a => h(a))            = FMCounter[C](b: C, k0+k1+2: Int)

          substitution: FMCounter[C](c: C, k0+k1+2: Int) == FMCounter[C](c: C, k0+k1+2: Int)
          TRUE
FMCounter 통계 호출 flatMap 횟수 때문에 표현식이 같고 다른 flatMap 개의 속성을 파괴합니다.
두 가지 법칙을 위반했기 때문에 같은 코드를 정확하게 작성하여 서로 다른 계수flatMap를 초래할 수 있는 여러 가지 방법이 있다.이것은 우리가 찾고 있는 해결 방안이 아닐 수도 있다는 것을 의미한다.하지만 그것은 명세서가 아니다.

결론


만약 당신이 특정한 문제에 직면하게 된다면, 예를 들어 원조로 돌아가는 함수를 한데 묶으면, 당신은 정말로 스스로 명세서를 발명했을 것이다.Monad는 그저 도구일 뿐, 서투른 해결 방안을 더욱 자연스럽게 보일 수 있다.우리는 이미 줄곧 명세서를 사용하고 있기 때문에 왜 그들이 이렇게 무엇을 잘하는지 이해할 필요가 있다.

좋은 웹페이지 즐겨찾기