.author{grid-column:1;grid-row:3;align-content:flex-start}.Metadata_root__oCstk>.description,.Metadata_root__oCstk>.pills-list,.Metadata_root__oCstk>.stats,.Metadata_root__oCstk>.title{grid-column:1/span 2}.Metadata_root__oCstk>.title{margin-bottom:8px}.Metadata_root__oCstk>.stats{margin-bottom:12px}.Metadata_root__oCstk>.description{margin-top:12px}.Metadata_root__oCstk>.pills-list{margin-block-start:20px;margin-block-end:18px}.Metadata_root__oCstk>.actions{display:none}@media screen and (max-width:928px){.Metadata_root__oCstk{margin-block:8px 20px}.Metadata_root__oCstk>.stats{margin-bottom:16px}.Metadata_root__oCstk>.description{margin-top:0}.Metadata_root__oCstk>.author{grid-column:2;grid-row:5;margin-block-start:12px;margin-block-end:6px;justify-content:end}.Metadata_root__oCstk>.actions{display:flex;margin-top:16px;grid-column:1;grid-row:5}.Metadata_root__oCstk>.pills-list{margin-block-start:16px;margin-block-end:0}}.Metadata_root__oCstk .metadata-recs{grid-column:1/-1}.Heading_heading__3MAvZ{color:var(--blue-gray-900)}.Heading_h1__3k7S2{font-size:32px;font-weight:700}.Heading_h2__f9yvs{font-size:28px;font-weight:600}.Heading_h3__f1djd{font-size:24px}.Heading_h4__7tfLE{font-size:20px}.Heading_h5__jVM0l{font-size:16px;font-weight:400}.Heading_h6__uUTrd{font-size:14px;font-weight:400}.Title_root__svkHQ{color:var(--blue-gray-900);font-size:clamp(26px,1vw + 1rem,28px);font-weight:600;line-height:1.25;min-width:0;word-break:break-word}.Stats_root__p_BoZ{flex-wrap:wrap;display:flex;align-items:center;-moz-column-gap:6px;column-gap:6px;color:var(--blue-gray-600);white-space:nowrap;font-size:16px}.Stats_leftContent__588PR,.Stats_rightContent__8d0AF{display:flex;gap:6px}.Stats_root__p_BoZ span{font-size:16px}.Stats_root__p_BoZ .Stats_aiTag__zTzW8{margin-left:10px}@media screen and (max-width:928px){.Stats_root__p_BoZ span{line-height:1.5}.Stats_root__p_BoZ.Stats_extendedMetadata__wb62p .Stats_leftContent__588PR{width:100%}.Stats_root__p_BoZ .Stats_aiTag__zTzW8{margin:8px 0 0}.Stats_root__p_BoZ.Stats_extendedMetadata__wb62p .Stats_formatTypesBullet__xDv0L{display:none}}.Likes_root__WVQ1_{cursor:pointer;transition:color .2s ease-in-out;border-radius:4px}.Likes_root__WVQ1_:hover{color:var(--blue-gray-700)}.Tooltip_root__7FS0Y{background:var(--midnight-green-dark);border-radius:4px;box-shadow:0 .5px 5px rgba(0,0,0,.04),0 4px 11px rgba(0,0,0,.2);color:var(--white);font-weight:400;font-size:12px;line-height:15px;padding:6px 8px;opacity:0;visibility:hidden;animation:Tooltip_show__qVG5k .2s ease-in-out forwards;z-index:var(--popup-index)}.Tooltip_triggerWrapper___S2HG{flex-shrink:0;position:relative;align-items:center;justify-content:center}@keyframes Tooltip_show__qVG5k{to{opacity:1;visibility:visible}}.Tooltip_large__J4Fvl{padding:16px;display:flex;flex-direction:column;background:#fff;color:var(--black)}.AITag_tag__Xx37c{padding:0 12px;height:25px;border-radius:16px;background-color:#f0f2f9;color:#16171b;display:inline-flex;gap:8px;font-size:14px;line-height:1.5;font-weight:600}.AITag_tooltipContent__7JZR_{width:332px}.Author_root___6Bx5{--link-color:var(--blue-gray-800);position:relative;display:flex;align-items:center;gap:8px}.Author_link___lVxw{z-index:1;color:var(--link-color);font-weight:600;display:block}.Author_link___lVxw:before{content:"";position:absolute;inset:0}.Author_follow__Lw4TS{z-index:1}@media screen and (max-width:928px){.Author_link___lVxw:hover{color:var(--blue-gray-800)}}.Avatar_root__GNWHY{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;background-color:var(--white);color:var(--blue-gray-300);border-radius:50%;font-size:16px;font-weight:600;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;overflow:hidden}.Avatar_initials__EJfVt{color:var(--white);transition:background-color .2s ease-in-out}.Avatar_initials__EJfVt,.Avatar_initials__EJfVt:hover{background-color:var(--blue-gray-600)}.Avatar_image__Bbtll{width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.FollowButton_root__FxpBi{display:inline-flex;background-color:transparent;border:1px solid transparent;border-radius:4px;font-size:12px;padding:1px 6px;transition:background-color .2s ease-in-out,border-color .2s ease-in-out;cursor:pointer}.FollowButton_following__xKCww{border-color:#bf5905;color:#bf5905}.FollowButton_following__xKCww:hover{background-color:#ffead7;border-color:rgba(191,89,5,.5)}.FollowButton_follow__d_6u5{border-color:var(--celadon-blue-dark);color:var(--celadon-blue-dark)}.FollowButton_follow__d_6u5:hover{background-color:#eaf7ff;border-color:rgba(2,126,176,.5)}@media screen and (max-width:928px){.FollowButton_root__FxpBi{display:none}}.Description_root__kt4uq{--line-height:26px;position:relative}.Description_root__kt4uq.Description_clamped__PaV_1{padding-bottom:25px}.Description_root__kt4uq.Description_clamped__PaV_1 .Description_wrapper__hYE9_{mask-image:linear-gradient(to bottom,var(--white),transparent);-webkit-mask-image:linear-gradient(to bottom,var(--white),transparent)}.Description_wrapper__hYE9_{min-height:var(--line-height);display:-webkit-box;overflow:hidden;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:2}.Description_noClamp__1z7c5,.Description_wrapper__hYE9_.Description_expanded__lRamt{-webkit-line-clamp:unset;-webkit-mask-image:none;mask-image:none}.Description_wrapper__hYE9_.Description_expanded__lRamt{height:auto}.Description_wrapper__hYE9_ p{color:var(--blue-gray-600);font-size:18px;line-height:var(--line-height);white-space:pre-wrap;word-break:break-word}.Description_more__ChrRK{position:absolute;padding:0;bottom:0;height:26px}.Description_less__BvWbY{padding:0}.Description_hidden__a9QZJ{display:none}@media screen and (max-width:928px){.Description_more__ChrRK{right:0;background-color:#fff}.Description_root__kt4uq.Description_clamped__PaV_1{padding-bottom:0}.Description_less__BvWbY,.Description_more__ChrRK{height:var(--line-height)}}.PillsList_root__2EydN{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.Pill_root__IqOYH{--bg:color-mix(in srgb,var(--celadon-blue-dark),90% transparent);--color:var(--blue-gray-800);height:40px;display:inline-flex;align-items:center;gap:6px;background-color:var(--bg);border-radius:100vmax;color:var(--color);font-size:16px;font-weight:600;padding-inline:16px;transition:color .2s ease-in-out,background-color .2s ease-in-out,filter .2s ease-in-out;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;text-decoration:none;white-space:nowrap}.Pill_root__IqOYH:not(.Pill_selected__VPtHm):hover{filter:brightness(.6)}.Pill_root__IqOYH.Pill_selected__VPtHm{--bg:var(--blue-gray-900);--color:var(--white)}@media screen and (max-width:520px){.Pill_root__IqOYH{height:28px;padding-inline:12px;gap:4px;font-size:12px}.Pill_icon__xE_Cg{--size:18px!important}}.Actions_root__00yIC{display:flex;align-items:baseline;gap:6px}.ReadingModeTooltip_root__laf5h{width:277px}.ToggleButtonGroup_root__vtGE_{display:inline-flex;gap:4px;padding:5px 8px;border-radius:4px;background-color:var(--white);border:1px solid var(--blue-gray-200)}.ToggleButton_root__jGx6U{border-radius:4px;background-color:var(--white);color:var(--blue-gray-700);border:none;width:44px;height:34px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.ToggleButton_selected__51g6d{background-color:var(--blue-gray-200)}.SaveLoggedIn_icon__lk74r{color:var(--blue-gray-700)}.PopoverMenuContent_root__MsRtR{background:var(--white);box-shadow:0 .5px 5px rgba(0,0,0,.04),0 4px 11px rgba(0,0,0,.2);border-radius:4px;opacity:0;visibility:hidden;transition:opacity .2s ease-in-out,visibility .2s ease-in-out;z-index:1000;overflow-y:auto}.PopoverMenuContent_root__MsRtR.PopoverMenuContent_visible__O86I_{opacity:1;visibility:visible;transition-delay:0s}.PopoverMenuItem_item__iazpP{width:100%;display:flex;align-items:center;background-color:transparent;color:inherit;cursor:pointer;font-size:inherit;line-height:24px;padding:12px 16px;white-space:nowrap}.PopoverMenuItem_item__iazpP.PopoverMenuItem_highlight__inbqK,.PopoverMenuItem_item__iazpP:hover{background-color:var(--blue-gray-100)}.PopoverMenuSeparator_separator__UpSGw{width:calc(100% - 32px);height:1px;background-color:var(--blue-gray-300);margin:8px 16px}.SavePopover_popover__mZhIY{width:290px;max-height:220px;color:var(--blue-gray-800);font-weight:400;padding:6px 8px;font-size:12px;line-height:15px;overflow:hidden}.SavePopover_saveToNewList__MVBSu{display:flex;align-items:center;gap:6px;color:var(--midnight-green-dark);font-weight:600;font-size:18px;line-height:24px;padding:10px;width:100%}.SavePopover_addIcon__aJLJ3{color:var(--blue-gray-800)}.SavePopover_horizontalSeparator__bsI6Y{border:none;border-bottom:1px solid var(--blue-gray-300);margin:8px 0}.SavePopover_listsContainer__tGGtp{height:100%;max-height:144px;display:flex;flex-direction:column;align-items:center;overflow-y:auto}.SavePopover_saveToList__Fsfl9{color:var(--blue-gray-800);font-weight:400;font-size:16px;justify-content:space-between;line-height:24px;padding:12px 14px 12px 16px;width:100%}.SavePopover_saveToList__Fsfl9 span:not(.SavePopover_clampLines__Blxtm){display:flex;flex-direction:row;gap:35px;justify-content:space-between}.SavePopover_saveToList__Fsfl9 span.SavePopover_clampLines__Blxtm{display:inline-block;overflow:hidden;text-align:left;text-overflow:ellipsis;white-space:nowrap;width:180px}.SavePopover_listIcon__OVsXv{color:var(--blue-gray-900)}.SavePopover_noSavedLists__0Mh_H{color:var(--blue-gray-600);font-weight:400;font-size:16px;line-height:24px;padding:12px 16px;text-align:center}.SaveOptionsDrawer_drawerTrigger__Gb7nK{box-shadow:none;border:none;border-radius:4px;position:relative;z-index:7}.SaveOptionsDrawer_drawerTrigger__Gb7nK:active,.SaveOptionsDrawer_drawerTrigger__Gb7nK:active:focus,.SaveOptionsDrawer_drawerTrigger__Gb7nK:hover{border:none;box-shadow:none;background-color:transparent}.SaveOptionsDrawer_drawerTrigger__Gb7nK:focus{background:transparent}.SaveOptionsDrawerContent_drawerContent__J5JTL{margin:16px}.SaveOptionsDrawerContent_separator__UW5Rz{margin:8px 0}.SaveOptionsDrawerContent_itemsContainer__tCXw5{display:flex;flex-direction:column;margin-top:0;max-height:144px;overflow-y:auto}.SaveOptionsDrawerContent_drawerActionSecondary__7cq8j{font-weight:400;line-height:24px;padding:0 8px;margin:8px 0;width:100%}.SaveOptionsDrawerContent_drawerActionSecondary__7cq8j span{color:var(--blue-gray-800);justify-content:space-between;gap:30px}.SaveOptionsDrawerContent_drawerActionPrimary____QuR span{gap:14px}.SaveOptionsDrawerContent_drawerActionPrimary____QuR{color:var(--blue-gray-800);display:flex;font-weight:400;line-height:24px;align-items:center;justify-content:flex-start;padding:8px 4px;margin:14px 0}.SaveOptionsDrawerContent_drawerActionPrimary____QuR.SaveOptionsDrawerContent_newListButton__VzhfD{font-size:16px;font-weight:600}.SaveOptionsDrawerContent_addIcon__Wb2cq{color:var(--blue-gray-800)}.SaveOptionsDrawerContent_drawerActionSecondary__7cq8j span.SaveOptionsDrawerContent_clampLines__zfkfI{display:inline-block;overflow:hidden;text-align:left;text-overflow:ellipsis;white-space:nowrap;width:calc(100vw - 115px)}.SaveOptionsDrawerContent_listIcon__5dcfC{color:var(--blue-gray-900)}.SaveOptionsDrawerContent_noSavedLists__cpUBY{color:var(--blue-gray-600);font-weight:400;font-size:16px;line-height:24px;padding:12px 16px;text-align:center}.Separator_root__70Ime{--orientationMargin:0;background-color:var(--blue-gray-200);flex-shrink:0}.Separator_horizontal__czVEa{width:calc(100% - var(--orientationMargin) * 2);height:1px}.Separator_vertical__JYCCK{width:1px;height:calc(100% - var(--orientationMargin) * 2)}.SaveLoggedOut_icon__ny9X2{color:var(--blue-gray-700)}.Dropdown_root__Z78h8{display:inline-block;position:relative;color:inherit}.DropdownTrigger_trigger__SzsBj{display:flex;align-items:center;justify-content:center;background:transparent;border:none;font-size:inherit;padding:0;margin:0;cursor:pointer}.DropdownTrigger_trigger__SzsBj:active,.DropdownTrigger_trigger__SzsBj:focus,.DropdownTrigger_trigger__SzsBj:hover{background:transparent}.DropdownContent_content__3daFs{position:absolute;display:flex;flex-direction:column;align-items:flex-start;padding:8px 0;background-color:#fff;box-shadow:0 .5px 5px rgba(0,0,0,.039),0 3.75px 11px rgba(0,0,0,.19);border-radius:4px;color:var(--blue-gray-800);opacity:0;visibility:hidden;transition:transform .15s,opacity .15s,visibility 0s linear .15s;transform:scale(.95);z-index:var(--dropdown-index)}.DropdownContent_bottom-left__gioqM{top:calc(100% + 6px);left:0;transform-origin:top left}.DropdownContent_bottom-right__QJ94h{top:calc(100% + 6px);right:0;transform-origin:top right}.DropdownContent_top-left__O3Ryp{bottom:calc(100% + 6px);left:0;transform-origin:bottom left}.DropdownContent_top-right___Qe45{bottom:calc(100% + 6px);right:0;transform-origin:bottom right}.DropdownContent_content__3daFs.DropdownContent_open__6VoiP{visibility:visible;opacity:1;transform:none;transition-delay:0s}.DropdownItem_item__Sv0GT{width:100%;display:flex;align-items:center;background-color:transparent;color:inherit;cursor:pointer;font-size:inherit;line-height:24px;padding:12px 16px;white-space:nowrap}.DropdownItem_highlight__jO3zg,.DropdownItem_item__Sv0GT:hover{background-color:var(--blue-gray-100)}.DropdownSeparator_separator__EN82n{width:calc(100% - 32px);height:1px;background-color:var(--blue-gray-300);margin:8px 16px}.MoreDropdownButton_moreOptionsDropdown__GK_Uw{display:flex;align-items:center;justify-content:center;width:32px;height:32px}.MoreDropdownButton_item__t4HmI{gap:12px}.MoreDropdownButton_moreOptionsIcon__TpJLA{color:var(--blue-gray-700)}.MoreDropdownButton_icon__DxfY4{color:var(--blue-gray-800)}.MoreDropdownButton_tooltip__az4od{white-space:nowrap}.MetadataToolbar_root__c03ao{--shadow-opacity:0;display:grid;height:var(--metadata-toolbar-height);background-color:var(--white);z-index:var(--header-index)}.MetadataToolbar_wrapper__r7XEc{position:relative;display:grid;grid-template-columns:minmax(0,1fr) max-content 1fr;align-items:center;padding-inline-end:20px;padding-block:16px}.MetadataToolbar_underline__QQn0C{grid-column:1/-1;grid-row:2;height:1px;position:absolute;bottom:0;right:-20px;left:-30vw;background-color:var(--blue-gray-200);box-shadow:0 2px 4px 0 rgba(0 0 0/var(--shadow-opacity));animation:MetadataToolbar_reveal-shadow__5yBxP linear both;animation-timeline:scroll(block);animation-range:150px 450px}.MetadataToolbar_verticalMode__Bh759{position:sticky}.MetadataToolbar_title__jfTWv{font-size:18px;font-weight:600}.MetadataToolbar_actions__FB33C{width:-moz-max-content;width:max-content;justify-self:flex-end;display:flex;grid-column-end:-1}.MetadataToolbar_pageNumber__i6Bhj{display:inline-flex;align-items:center;margin-inline:1em;height:44px}.MetadataToolbar_isInReadingModeVariant__XMDcr{align-items:flex-start}.MetadataToolbar_isInReadingModeVariant__XMDcr .MetadataToolbar_downloadButton__ncS7o>button{padding:0 15px}@media screen and (max-width:928px){.MetadataToolbar_root__c03ao{display:none}}@media screen and (min-width:929px) and (max-width:1249px){.MetadataToolbar_isInReadingModeVariant__XMDcr .MetadataToolbar_pageNumber__i6Bhj{display:none}}@keyframes MetadataToolbar_reveal-shadow__5yBxP{to{--shadow-opacity:0.122}}.DownloadButton_root__adY00{margin-left:auto;display:inline-grid;gap:6px;justify-items:center;flex-shrink:0}.DownloadButton_savedStyling__k18od{font-weight:600;font-size:18px}.DownloadMultipleFormatDrawer_root__CWFxX{width:100%;padding:0 24px}.DownloadMultipleFormatDrawer_drawerHeading__8LnFw{margin:16px 0}.DownloadMultipleFormatDrawer_drawerContent__y815X{width:100%;padding:24px 0}.DownloadMultipleFormatDrawer_drawerRadioButtons__I_lQ4 label{margin-bottom:20px}.DownloadMultipleFormatDrawer_drawerRadioButtons__I_lQ4{margin-bottom:4px}.Fieldset_root__L2NQU{display:grid;padding:0;border:0}.Fieldset_root__L2NQU legend{display:none}.DownloadMultipleFormatPopover_popoverContent__IJudF{min-width:185px}.FadeInOut_root__v7Efq{position:relative;min-width:0;background-color:var(--snow-gray);padding:20px}.FadeInOut_root__v7Efq.FadeInOut_isInfographic__PdX2K{background-color:unset;padding-inline-start:0;padding-inline-end:20px}@media (max-width:928px){.FadeInOut_root__v7Efq,.FadeInOut_root__v7Efq.FadeInOut_isInfographic__PdX2K{background-color:unset;padding-inline:2px}}.FadeInOut_in__9dYWz{animation:FadeInOut_fadeIn__14JD0 .5s}.FadeInOut_out___eDZk{animation:FadeInOut_fadeOut__ILQaD .4s}@keyframes FadeInOut_fadeIn__14JD0{0%{opacity:0}to{opacity:1}}@keyframes FadeInOut_fadeOut__ILQaD{0%{opacity:1}to{opacity:0}}.VerticalSlideOverlayed_root__9Thd4{position:relative}.VerticalSlideOverlayed_root__9Thd4 .vertical-slide-image{position:absolute;inset:0}.VerticalSlideOverlayed_active___p5f2 .vertical-slide-image{opacity:.4}.VerticalSlideOverlayed_active___p5f2{background-color:#000}.VerticalSlideImage_image__VtE4p{width:100%;height:100%;-o-object-fit:contain;object-fit:contain;box-shadow:0 0 0 1px var(--blue-gray-200);border-radius:8px;transition:opacity .3s cubic-bezier(.2,0,0,1);opacity:.9;overflow:clip;background-color:var(--blue-gray-600);animation-name:VerticalSlideImage_pulse__OPBSn;animation-direction:alternate;animation-duration:1s;animation-iteration-count:infinite;animation-timing-function:ease-in-out}.VerticalSlideImage_image__VtE4p.VerticalSlideImage_isLoaded__r4xAa{opacity:1;animation:none;background-color:unset}@keyframes VerticalSlideImage_pulse__OPBSn{0%{opacity:.09}to{opacity:.15}}.SlideScrollDetector_root__AIK38{position:relative;height:100%;width:100%;pointer-events:none}.SlideScrollDetector_detector__rhkyk{position:absolute;top:0;left:0;height:1px;width:100%}.SlideScrollDetector_detector1__NmF8s{top:25%}.SlideScrollDetector_detector2__32XWr{top:75%}.SlideActions_root__fB9Q4{position:absolute;right:0;top:0;display:grid;grid-template-columns:40px;grid-template-rows:40px 40px;grid-column-gap:12px;-moz-column-gap:12px;column-gap:12px;grid-row-gap:12px;row-gap:12px;padding:12px;justify-content:flex-end;align-items:center;text-align:end}.SlideActions_active__aD_e1{grid-template-columns:max-content 40px}.SlideActions_button__o8UXk{background-color:var(--blue-gray-100);color:var(--blue-gray-700)}.VerticalSlide_infographic__ij1FA,.VerticalSlide_root__jU_9r{position:relative}.VerticalSlide_root__jU_9r .vertical-slide-image{position:absolute;inset:0}.VerticalSlide_infographic__ij1FA .vertical-slide-image{position:unset}.VerticalPlayer_root__K8_YS{position:relative;display:grid;grid-template-columns:minmax(0,1fr);grid-gap:24px;gap:24px}@media screen and (max-width:928px){.VerticalPlayer_root__K8_YS{gap:8px}}.FreestarVideoAd_root__KDWgl{min-width:0;flex-shrink:0;aspect-ratio:16/9}.VerticalInterstitialAdWrapper_root__LxQh8{container-type:inline-size;position:relative;display:grid;min-height:280px;overflow-x:clip}.VerticalInterstitialAdWrapper_root__LxQh8:has(.interstitial-ad-container.has-fetched):not(:has(.interstitial-ad-container.has-fetched .freestar-ad-container:not(.unfilled))){display:none}.VerticalInterstitialAdWrapper_root__LxQh8:has(div.interstitial-ad-container.has-fetched div.freestar-ad-container.filled div),.VerticalInterstitialAdWrapper_root__LxQh8:has(div.interstitial-ad-container.has-fetched div.freestar-ad-container.unfilled iframe){display:block!important}.VerticalInterstitial_root__Dunl7{display:none}@media (max-width:520px){.VerticalInterstitial_root__Dunl7:not(.VerticalInterstitial_inVariant__xB9lL){display:block}}@container (max-width: 480px){.VerticalInterstitial_root__Dunl7.VerticalInterstitial_inVariant__xB9lL{display:block}}.FreestarAdContainer_root__qPPC_{position:relative;display:grid;place-content:center}.FreestarAdContainer_root__qPPC_.FreestarAdContainer_withFallback__A4lgm{aspect-ratio:var(--fallback-aspect-ratio)}.FreestarAdContainer_fallback__WreT9{position:absolute;inset:0;grid-template-columns:unset;place-content:center}.AdFallback_root__uAXsl{display:grid;justify-items:center;grid-template-columns:1fr;z-index:0}.MultipleIncontentSmall_root__x58Hs{display:none;grid-template-columns:repeat(auto-fit,minmax(max-content,300px));place-content:center;gap:16px}@media (min-width:521px){.MultipleIncontentSmall_root__x58Hs:not(.MultipleIncontentSmall_inVariant__uf1S8){display:grid}}@container (max-width: 616px){.MultipleIncontentSmall_root__x58Hs:not(.MultipleIncontentSmall_inVariant__uf1S8) .freestar-ad-container:nth-of-type(2){display:none}}@container (min-width: 616px) and (max-width: 688px){.MultipleIncontentSmall_root__x58Hs.MultipleIncontentSmall_inVariant__uf1S8{display:grid}}.MultipleIncontentWide_root__4WD8U{display:none;place-content:center}@container (min-width: 480px) and (max-width: 616px){.MultipleIncontentWide_root__4WD8U{display:grid}}.MultipleIncontentLarge_root__pGIAn{display:none;grid-template-columns:repeat(auto-fit,minmax(max-content,336px));place-content:center;gap:16px}@container (min-width: 688px){.MultipleIncontentLarge_root__pGIAn{display:grid}}.SlideRecs_root__likA5{--card-hover-background:var(--blue-gray-200);display:flex;flex-direction:column;gap:20px}.SlideRecs_root__likA5 h2{font-size:24px;font-weight:600}@media (max-width:1050px){.SlideRecs_root__likA5{display:none}}.SlideRecs_root__likA5 .SlideRecs_cards__Lbxtt{display:grid;grid-gap:16px;gap:16px}.SlideRecs_root__likA5 .SlideRecs_card__txc2D{position:relative;grid-template-columns:180px 1fr;grid-template-rows:auto;gap:16px}.SlideRecs_root__likA5 .slideshow-thumbnail{box-shadow:0 0 0 1px var(--blue-gray-200)}.SlideRecs_root__likA5 .slideshow-card-content{padding-block:0;padding-inline-end:36px;gap:8px}.SlideRecs_root__likA5 .slideshow-title{line-height:1;margin-block-end:0}.SlideRecs_root__likA5 .SlideRecs_wrapper__21j_w{display:flex;gap:8px;align-items:center}.SlideRecs_root__likA5 .SlideRecs_wrapper__21j_w a,.SlideRecs_root__likA5 .SlideRecs_wrapper__21j_w span{font-size:12px}.SlideRecs_root__likA5 .SlideRecs_save__RR8dD{position:absolute;top:8px;right:8px}.SlideRecs_root__likA5 .SlideRecs_author__zlhWO{color:var(--blue-gray-700);font-weight:600;text-decoration:none;z-index:2}.SlideRecs_root__likA5 .SlideRecs_author__zlhWO:first-letter{text-transform:uppercase}.SlideRecs_root__likA5 .SlideRecs_card__txc2D .slideshow-title{font-size:16px}.SlideRecs_root__likA5 .SlideRecs_description__0bGsx{color:var(--blue-gray-700);font-size:14px;display:-webkit-box;overflow:hidden;line-clamp:1;-webkit-line-clamp:1;-webkit-box-orient:vertical}.SlideRecs_root__likA5 .SlideRecs_dot__Db7HR{font-size:16px}.SlideRecs_root__likA5 .SlideRecs_tags__RCA1q{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.SlideRecs_root__likA5 .SlideRecs_tags__RCA1q span{display:grid;place-content:center;height:20px;background-color:var(--alice-blue-600);border-radius:100vmax;color:var(--blue-gray-700);font-size:11px;font-weight:600;padding-inline:12px;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap;text-transform:capitalize;z-index:2}.RecWithPopover_root__iNXfW{width:330px;display:flex;flex-direction:column;gap:8px}.RecWithPopover_root__iNXfW>*{line-height:18px}.RecWithPopover_root__iNXfW h3{font-size:16px;font-weight:600}.RecWithPopover_root__iNXfW p{flex:1 1;color:var(--blue-gray-600);font-size:14px;max-width:100%;display:-webkit-box;line-clamp:5;-webkit-line-clamp:5;-webkit-box-orient:vertical;overflow-y:hidden}.RecWithPopover_hasSiblingsWithPopover__dlaxN{border-radius:4px;padding:12px}.RecWithPopover_hasPopover__G8_rr:hover:not(:has(:is(.save:hover,.author:hover))){background-color:var(--card-hover-background)}.SlideshowCard_root__pD8t4{position:relative;display:grid;grid-template-rows:max-content minmax(0,1fr);grid-template-columns:minmax(0,1fr);align-content:flex-start;color:var(--blue-gray-600)}.SlideshowCard_root__pD8t4:hover .SlideshowCard_thumb__86aJk{scale:1.02}.SlideshowCard_content__xh7kV{display:flex;flex-direction:column;-moz-column-gap:8px;column-gap:8px;padding:16px 0}.SlideshowCardLink_root__p8KI7{position:absolute;inset:0;z-index:1;margin:4px}.Thumbnail_root__qLW0K{--ease:cubic-bezier(0.2,0,0,1);position:relative;background-color:var(--blue-gray-100);border:1px solid var(--blue-gray-100);border-radius:8px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);scale:1;transition:scale .2s var(--ease);overflow:hidden}.Thumbnail_thumb__UXO3a{--reveal-delay:calc(30ms * var(--index));position:absolute;inset:0;width:100%;height:100%;opacity:0;transition:opacity .3s var(--ease);transition-delay:var(--reveal-delay)}.Thumbnail_loaded__XOJ5p{opacity:1}.Thumbnail_blur__opK6A{filter:blur(8px)}.Thumbnail_cover__1zsIi{-o-object-fit:cover;object-fit:cover}.Thumbnail_contain__K6M0d{-o-object-fit:contain;object-fit:contain}.SlideshowTitle_root__2VccW{display:-webkit-box;color:var(--blue-gray-900);font-size:18px;font-weight:600;line-height:1.2;margin-bottom:8px;white-space:break-spaces;word-break:break-word;-webkit-box-orient:vertical}.RecSaveButton_root__0CS9m{grid-area:d}.RecSaveButton_icon__btwCp{fill:currentColor;z-index:2}@media (max-width:928px){.RecSaveButton_root__0CS9m{display:none}}.SlideshowAuthor_root__IkT1_{color:var(--celadon-blue);font-weight:600;text-decoration:underline;-webkit-text-decoration-color:transparent;text-decoration-color:transparent;text-decoration-thickness:1.5px;text-underline-offset:2px;transition:-webkit-text-decoration-color .2s ease-out;transition:text-decoration-color .2s ease-out;transition:text-decoration-color .2s ease-out,-webkit-text-decoration-color .2s ease-out;z-index:2}.SlideshowAuthor_root__IkT1_:hover{-webkit-text-decoration-color:var(--celadon-blue);text-decoration-color:var(--celadon-blue)}.CountTag_root__y1hE1,.SplitDot_root__lTZDc{color:var(--blue-gray-600);font-weight:400}.SlideshowStats_root__EQOR1{display:flex;align-items:center;gap:6px}.SlideshowStats_text___WD7l{color:var(--blue-gray-600)}.BelowReaderAd_root__NKeGg{margin-top:16px;margin-bottom:60px}.BelowReaderAd_root__NKeGg.BelowReaderAd_desktop__7_JN7{display:block;justify-items:flex-start}.BelowReaderAd_root__NKeGg.BelowReaderAd_mobile__08T3d{display:none}.BelowReaderAd_root__NKeGg .fallback-ad{justify-self:flex-start}@media screen and (max-width:928px){.BelowReaderAd_root__NKeGg.BelowReaderAd_desktop__7_JN7{display:none}.BelowReaderAd_root__NKeGg.BelowReaderAd_mobile__08T3d{display:block;justify-items:center}}.Sidebar_root__1BbNu{width:var(--sidebar-size);max-height:100dvh;overflow:clip scroll;position:sticky;top:var(--metadata-toolbar-offset,0);display:flex;flex-direction:column;padding-inline-start:28px;padding-block-start:32px;padding-block-end:430px}.Sidebar_root__1BbNu.Sidebar_withSidebarAds__0w0dT{max-height:unset;overflow:unset;position:static;top:unset;display:grid;grid-template-rows:repeat(var(--slots),1fr);padding-inline:28px}@media (max-width:1050px){.Sidebar_root__1BbNu,.Sidebar_root__1BbNu.Sidebar_withSidebarAds__0w0dT{display:none}}.AboveRecsAd_root__iTmTR{min-height:280px;margin-block-end:32px}.AboveRecsAd_root__iTmTR .freestar-ad-container{place-content:flex-start}@media (max-width:1050px){.AboveRecsAd_root__iTmTR .freestar-ad-container{place-content:center}}.AboveRecsAd_mobileAd__LYgqf{display:none}.AboveRecsAd_desktopAd__ymykj{display:block}@media (max-width:1050px){.AboveRecsAd_mobileAd__LYgqf{display:block}.AboveRecsAd_desktopAd__ymykj{display:none}}.RailRecommendations_root__zqtZQ{display:flex;flex-direction:column;gap:8px;padding-block-end:24px}.RailRecommendations_title__kt1D2{font-size:24px;color:var(--blue-gray-900);font-weight:600;margin-block-end:20px}@media only screen and (min-width:929px){.RailRecommendations_hidden__7Ct2B{display:none}}.RailCard_root__rZUGY{--card-hover-background:var(--blue-gray-100);width:100%;position:relative;border-radius:8px;padding-block:12px;padding-inline:8px}.RailCard_root__rZUGY.slideshow-card{grid-template-columns:160px minmax(0,1fr);grid-template-rows:auto;gap:12px}.RailCard_root__rZUGY .slideshow-card-content{padding:0}.RailCard_actionContainer__eXUHT{display:grid;grid-template-columns:minmax(0,1fr) 28px;grid-column-gap:8px;-moz-column-gap:8px;column-gap:8px}.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-author,.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-stats,.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-title{grid-area:unset}.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-author,.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-stats{grid-column:1/-1}.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-author{grid-row:2}.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-stats{grid-row:3}.RailCard_link__d3BBm{z-index:1}.RailCard_info__Oqm6G{min-width:0;display:flex;flex-direction:column;justify-content:center;word-break:break-word}.RailCard_root__rZUGY .RailCard_title__Tvfiv{font-size:16px;margin-bottom:0;grid-area:a;word-break:break-word}.RailCard_root__rZUGY .RailCard_stats__ZvZms{margin-top:12px}.RailCard_stats__ZvZms .text{font-size:12px}.RailCard_root__rZUGY .RailCard_author__JYeYZ{color:var(--blue-gray-700);margin-top:8px;text-decoration:none}@media screen and (min-width:929px){.RailCard_root__rZUGY:hover{background-color:var(--blue-gray-100)}}.RelatedContent_root__29Np1{background-color:var(--blue-gray-100);border-top:1px solid var(--blue-gray-200);border-bottom:1px solid var(--blue-gray-200);padding-block:32px;position:relative}.RelatedContent_wrapper__riU7l{display:grid;grid-template-columns:minmax(0,1fr);grid-gap:32px;gap:32px;max-width:var(--max-content-width);margin-inline:auto}.RelatedContent_title__QUhpL{text-align:center;font-size:32px;font-weight:700}.RelatedContent_root__29Np1 .bottom-recs{display:grid}.RelatedContent_root__29Np1 .rail-recs{display:none}@media screen and (max-width:520px){.RelatedContent_root__29Np1 .bottom-recs{display:none}.RelatedContent_root__29Np1 .rail-recs{display:flex}.RelatedContent_wrapper__riU7l{padding-inline:16px}}.BottomRecommendation_root__7aU9w{display:grid;grid-gap:4px;gap:4px;padding-inline:24px}.BottomRecommendation_title__SRj68{font-size:22px;font-weight:600}.BottomRecommendation_count__4HpLo{color:var(--blue-gray-600);font-size:16px;font-weight:400}.Slider_root__c0Jo8{position:relative;display:grid}.Slider_scroller__KHjw4{display:flex;gap:20px;overflow:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;max-inline-size:100%;min-block-size:100%;touch-action:pan-x;-ms-overflow-style:none;scrollbar-width:none}.Slider_scroller__KHjw4::-webkit-scrollbar{display:none}.Slider_scroller__KHjw4>*{flex-grow:1;flex-shrink:0;scroll-snap-align:start}.Slider_scroller__KHjw4>:last-child{scroll-snap-align:end}.Slider_arrow__8LCca{display:grid;place-content:center;width:36px;height:36px;position:absolute;top:50%;background:var(--white);border:1px solid var(--blue-gray-200);border-radius:100vmax;box-shadow:0 2px 4px 0 rgba(0,0,0,.25);color:var(--blue-gray-800);padding:0;opacity:1;visibility:visible;pointer-events:all;transition:opacity .2s ease-in-out,visibility .2s ease-in-out;transition-delay:0s;cursor:pointer;z-index:1}.Slider_prev__YMssa{left:0;translate:-50% -50%}.Slider_next__fa9IO{right:0;translate:50% -50%}.Slider_hidden__rs7nK{opacity:0;visibility:hidden;pointer-events:none}.BottomRecommendationCard_root__gffTk{inline-size:clamp(220px,12.63rem + 3.45vw,260px);position:relative;border-radius:8px;background-color:#fff;border:1px solid var(--blue-gray-200)}.BottomRecommendationCard_root__gffTk .slideshow-thumbnail{border-radius:0;border-top-left-radius:8px;border-top-right-radius:8px}.BottomRecommendationCard_root__gffTk .slideshow-card-content{padding:16px}.BottomRecommendationCard_root__gffTk .slideshow-author{max-width:50%;word-break:break-all;white-space:break-spaces;-webkit-line-clamp:1;display:-webkit-box;-webkit-box-orient:vertical}.BottomRecommendationCard_root__gffTk .slideshow-stats{margin-block-start:auto}.BottomRecommendationCard_root__gffTk:focus,.BottomRecommendationCard_root__gffTk:focus-visible{outline:none;border-color:var(--celadon-blue);border-width:1px}.BottomRecommendationCard_root__gffTk .BottomRecommendationCard_text__5jKNE{display:-webkit-box;-webkit-box-orient:vertical;white-space:break-spaces;word-break:break-word;-webkit-line-clamp:1}.BottomRecommendationCard_link__pHORq:before{content:"";position:absolute;inset:0;z-index:1}.BottomRecommendationCard_metaLine__shwPk{display:flex;gap:8px;align-items:center;white-space:nowrap;grid-area:c}.ScribdRecommendation_root__t3ezS{display:grid;grid-gap:16px;gap:16px;padding:0 24px}.ScribdRecommendation_header__Jw_M1{display:grid;grid-template-columns:1fr max-content}.ScribdRecommendation_title__JZ5p7{font-size:22px;font-weight:600}.ScribdRecommendation_link__4DVQz{align-self:end;grid-column:2;grid-row:1/span 2;color:var(--celadon-blue-dark)}.ScribdRecommendation_link__4DVQz:hover{color:var(--celadon-blue)}.ScribdRecommendationCard_root__ef2Y_{--ease:cubic-bezier(0.2,0,0,1);--rec-bg-1:#f1e3e3;--rec-bg-2:#f6f4e3;--rec-bg-3:#e1eaec;--rec-bg-4:#efebef;--rec-bg-5:#f1f0f0;--rec-bg-6:#f4eadb;--rec-bg-7:#eaeee7;--rec-bg-8:#e3e8ef;--rec-bg-9:#f1eee6;max-width:172px;position:relative;height:100%;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:max-content minmax(0,1fr);align-content:flex-start;background-color:var(--white);border:1px solid var(--blue-gray-200);box-shadow:none;transition:box-shadow .2s ease-in-out;text-decoration:none}.ScribdRecommendationCard_root__ef2Y_:hover{box-shadow:0 2px 10px rgba(0,0,0,.1)}.ScribdRecommendationCard_thumb__5VVNh{position:relative;display:flex;background-color:var(--blue-gray-200);margin-block-start:24px;margin-inline:20px}.ScribdRecommendationCard_root__ef2Y_:before{position:absolute;inset:0;aspect-ratio:19/16;content:"";background-color:var(--block-color)}.ScribdRecommendationCard_thumb__5VVNh img{--reveal-delay:calc(30ms * var(--card-index));aspect-ratio:inherit;box-shadow:0 4px 6px rgba(0,0,0,.2);-o-object-fit:cover;object-fit:cover;opacity:0;visibility:hidden;transition:opacity .3s var(--ease);transition-delay:var(--reveal-delay)}.ScribdRecommendationCard_loaded__FTN_f img{opacity:1;visibility:visible}.ScribdRecommendationCard_content__ObcvL{display:flex;flex-direction:column;padding-block:20px;padding-inline:16px}.ScribdRecommendationCard_content__ObcvL .rating{padding-block-start:8px;margin-block-start:auto}@media (max-width:928px){.ScribdRecommendationCard_content__ObcvL .rating{flex-direction:column;align-items:flex-start}}.Rating_root__fgZQJ{display:flex;align-items:center;-moz-column-gap:6px;column-gap:6px;white-space:nowrap}.Rating_root__fgZQJ :first-child{color:#e47b01;font-size:16px;line-height:1}.Rating_root__fgZQJ :last-child{color:var(--blue-gray-600);font-size:14px;letter-spacing:-.25px}.Transcript_root__Vrf6Q{width:100%;max-width:var(--max-content-width);display:grid;grid-gap:8px;gap:8px;padding:32px 16px;margin-inline:auto;position:relative;background:#fff}.Transcript_title__YgAka{display:flex;align-items:center;gap:4px;font-weight:300;word-break:break-word}.Transcript_list__faItj{list-style-type:none;padding-inline-start:0;word-break:break-word}.Transcript_link__MLbGS{color:var(--celadon-blue);font-weight:700;line-height:22px;text-decoration:none;cursor:pointer}.EditorsNotes_root__3PcDF{padding:32px 16px;margin:0 auto}.EditorsNotes_heading__XR9E6{font-weight:700;font-size:22px}.EditorsNotes_list__NcG5Y{padding-left:30px;font-size:18px;font-style:italic;color:var(--blue-gray-600)}.EditorsNotes_item__ebBbj{word-break:break-word}@media screen and (min-width:1696px){.EditorsNotes_root__3PcDF{max-width:1688px}}.FixedDownloadButton_root__14xtQ{display:none}@media screen and (max-width:928px){.FixedDownloadButton_root__14xtQ{position:sticky;bottom:0;display:flex;justify-content:right;z-index:3;padding:16px}}.Modal_root__TYkzh[open]{opacity:1;animation:Modal_slide-in__GHXut .3s ease-out}.Modal_root__TYkzh{--max-height:calc(100dvb - var(--header-height));--slide-from:calc(-50% + 8px);--slide-to:-50%;--title-size:80px;max-width:100%;max-height:var(--max-height);top:50%;left:50%;translate:-50% -50%;box-shadow:0 0 0 1px rgba(9,30,66,.08),0 2px 1px rgba(9,30,66,.08),0 0 20px -6px rgba(9,30,66,.3);border:0;border-radius:var(--border-radius);padding:0;opacity:0;animation:Modal_slide-out__m_Ov2 .2s ease-in;transition:display allow-discrete .3s,overlay allow-discrete .3s;overflow:clip}@starting-style{.Modal_root__TYkzh[open]{opacity:0}}.Modal_root__TYkzh.Modal_small__hupRE{width:400px}.Modal_root__TYkzh.Modal_medium__j8NOV{width:600px}.Modal_root__TYkzh.Modal_large__ygVmr{width:800px}.Modal_root__TYkzh.Modal_xlarge__HeXWk{width:960px}.Modal_wrapper__4UTGq{position:relative;display:flex;flex-direction:column}.Modal_wrapper__4UTGq>h1+*{flex:1 1;max-height:calc(var(--max-height) - var(--title-size));overflow:clip auto}@media screen and (max-width:520px){.Modal_root__TYkzh,.Modal_root__TYkzh.Modal_large__ygVmr,.Modal_root__TYkzh.Modal_medium__j8NOV,.Modal_root__TYkzh.Modal_small__hupRE,.Modal_root__TYkzh.Modal_xlarge__HeXWk{width:100vw}}@media screen and (max-width:928px){.Modal_root__TYkzh.Modal_bottomPlacement__BUbfp{--slide-from:8px;--slide-to:0;width:100vw;top:unset;bottom:0;translate:-50% 0;border-bottom-left-radius:0;border-bottom-right-radius:0}}@keyframes Modal_slide-in__GHXut{0%{translate:-50% var(--slide-from);opacity:0}to{translate:-50% var(--slide-to);opacity:1}}@keyframes Modal_slide-out__m_Ov2{0%{translate:-50% var(--slide-to);opacity:1}to{translate:-50% var(--slide-from);opacity:0}}.Modal_root__TYkzh::backdrop{background-color:transparent;transition:display allow-discrete .3s,overlay allow-discrete .3s,background-color .3s}.Modal_root__TYkzh[open]::backdrop{background-color:rgba(0,0,0,.6)}@starting-style{.Modal_root__TYkzh[open]::backdrop{background-color:transparent}}.Modal_title__xhSfl{height:var(--title-size);display:flex;align-items:center;color:var(--blue-gray-900);font-size:20px;font-weight:600;border-bottom:1px solid var(--blue-gray-200);padding-inline-start:20px;padding-inline-end:60px}.Modal_title__xhSfl:first-letter{text-transform:capitalize}.Modal_content__R1F4d{padding-inline:20px;padding-block:24px}.Modal_actions__t63hZ{display:flex;align-items:center;justify-content:flex-end;gap:24px;padding-inline:20px;padding-block:16px}.CloseButton_root__JCTRm{position:absolute;right:16px;top:16px;width:40px;height:40px;display:grid;place-content:center;color:var(--blue-gray-600);border-radius:100vmax;background-color:transparent;border:0;padding:0;margin:0;transition:background-color .2s ease-in-out;cursor:pointer}.CloseButton_root__JCTRm:hover{background-color:rgba(var(--blue-gray-600-rgb),.05)}.ReportForm_root__RNqAc{display:grid;grid-gap:10px;gap:10px}.ReportForm_selectField__Kyyaj{max-width:unset!important}.LikeModal_more__R9uAk{justify-self:center;color:var(--celadon-blue);font-weight:500;opacity:0;visibility:hidden;transition:opacity .2s ease-in-out,visibility .2s ease-in-out}.LikeModal_more__R9uAk.LikeModal_visible__t1vr4{opacity:1;visibility:visible;transition-delay:0s}.LikesUserList_root__RMFUk{list-style:none;padding:0 0 16px;margin:0}.LikesUserList_root__RMFUk>li{display:grid}.LikesUserList_root__RMFUk>li:last-child .LikesUserList_link__NeMA0{border-bottom:none}.LikesUserList_link__NeMA0{display:grid;grid-template-columns:repeat(2,max-content) 1fr;grid-template-rows:repeat(2,min-content);grid-template-areas:"avatar username summary" "avatar title title";grid-column-gap:12px;-moz-column-gap:12px;column-gap:12px;grid-row-gap:0;row-gap:0;border-bottom:1px solid var(--blue-gray-200);padding:8px 0;text-decoration:none}.LikesUserList_avatar__VRXz2{grid-area:avatar}.LikesUserList_username__c84om{grid-area:username}.LikesUserList_summary___gbSG{grid-area:summary}.LikesUserList_title__UF0V6{grid-area:title}.ViewModal_content__GPKXy{padding-block:0}.ViewModal_row__xLxnz{display:flex;justify-content:space-between;border-bottom:1px dashed var(--blue-gray-300);color:var(--blue-gray-800);padding:16px 0}.ViewModal_row__xLxnz:last-child{border-bottom:none}.ConfirmRemoveSavedModal_description__2EAEu{color:var(--blue-gray-800);font-weight:400;font-size:16px;line-height:20px;padding:24px 0}div.SaveToNewListModal_input__Fi90k{max-width:unset;margin-bottom:24px}.SaveToNewListModal_checkboxWrapper__y_w0m .SaveToNewListModal_checkboxLabel__lMiU9:hover,.SaveToNewListModal_checkboxWrapper__y_w0m:hover .SaveToNewListModal_checkboxLabel__lMiU9,.SaveToNewListModal_checkboxWrapper__y_w0m:hover .SaveToNewListModal_input__Fi90k{color:var(--blue-gray-800)}.SaveToNewListModal_checkboxWrapper__y_w0m .SaveToNewListModal_checkboxLabel__lMiU9{color:var(--blue-gray-800);font-weight:400;font-size:14px;flex-direction:column;display:flex;line-height:18px}.SaveToNewListModal_imageContainer__Dx4nD{display:flex;flex-direction:column;justify-content:center;align-items:center;width:178px;margin:0 auto 24px}.SaveToNewListModal_imageContainer__Dx4nD img{height:100px}.SaveToNewListModal_errorContainer__FBZPH{margin:16px 0}
Svoboda | Graniru | BBC Russia | Golosameriki | Facebook
SlideShare a Scribd company logo
CHEAT-SHEET
Folding
#6
∶
/ 
𝒂𝟎 ∶
/ 
𝒂𝟏 ∶
/ 
𝒂𝟐 ∶
/ 
𝒂𝟑
𝒇
/ 
𝒂𝟎 𝒇
/ 
𝒂𝟏 𝒇
/ 
𝒂𝟐 𝒇
/ 
𝒂𝟑 𝒆
@philip_schwarz
slides by https://fpilluminated.com/
𝑓𝑜𝑙𝑑𝑙 is tail recursive.
Folding a list (however large) with 𝑓𝑜𝑙𝑑𝑙
does not result in a stack overflow error. ‡
𝑓𝑜𝑙𝑑𝑟 is not tail recursive. ☩
Folding a sufficiently large list with 𝑓𝑜𝑙𝑑𝑟
results in a stack overflow error. †
† See slide after next for an exception in Scala
‡ See next slide for an exception in Haskell
☩ See slides five to nine for a refresher on tail recursion
𝑓𝑜𝑙𝑑𝑙 ∷ 𝛽 → 𝛼 → 𝛽 → 𝛽 → 𝛼 → 𝛽
𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 = 𝑏
𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑓 𝑏 𝑥 𝑥𝑠
𝑓𝑜𝑙𝑑𝑟 ∷ 𝛼 → 𝛽 → 𝛽 → 𝛽 → 𝛼 → 𝛽
𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 = 𝑏
𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓 𝑥 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥𝑠
𝑓𝑜𝑙𝑑𝑟 ∷ 𝛼 → 𝛽 → 𝛽 → 𝛽 → 𝛼 → 𝛽
𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 = 𝑏
𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓 𝑥 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥𝑠
𝑓𝑜𝑙𝑑𝑙 ∷ 𝛽 → 𝛼 → 𝛽 → 𝛽 → 𝛼 → 𝛽
𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 = 𝑏
𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑓 𝑏 𝑥 𝑥𝑠
> :{
| foldRight :: (α -> β -> β) -> β -> [α] -> β
| foldRight f e [] = e
| foldRight f e (x:xs) = f x (foldRight f e xs)
| :}
> foldRight (+) 0 [1..10000000]
50000005000000
> foldRight (+) 0 [1..100000000]
*** Exception: stack overflow
> -- same again but using built-in function
> foldr (+) 0 [1..10000000]
50000005000000
> foldr (+) 0 [1..100000000]
*** Exception: stack overflow
> :{
| foldLeft :: (β -> α -> β) -> β -> [α] -> β
| foldLeft f e [] = e
| foldLeft f e (x:xs) = foldLeft f (f e x) xs
| :}
> foldLeft (+) 0 [1..10000000]
50000005000000
> foldLeft (+) 0 [1..100000000]
*** Exception: stack overflow
> -- same again but using built-in function
> foldl (+) 0 [1..10000000]
50000005000000
> foldl (+) 0 [1..100000000]
*** Exception: stack overflow
> Data.List.foldl' (+) 0 [1..100000000]
5000000050000000
These stack overflows have to do with Haskell’s nonstrict evaluation, and are avoided using a strict left fold, called foldl’ (see final slides).
scala> def foldr[A,B](f: A=>B=>B)(e:B)(s:List[A]):B =
| s match { case Nil => e
| case x::xs => f(x)(foldr(f)(e)(xs)) }
scala> def `(+)`: Long => Long => Long =
| m => n => m + n
scala> foldr(`(+)`)(0)(List(1,2,3,4))
val res1: Int = 10
scala> foldr(`(+)`)(0)(List.range(1,10_001))
val res2: Int = 50005000
scala> foldr(`(+)`)(0)(List.range(1,100_001))
java.lang.StackOverflowError
scala> // same again but using built-in function
scala> List.range(1,10_001).foldRight(0)(_+_)
val res3: Int = 50005000
scala> List.range(1,100_001).foldRight(0L)(_+_)
val res4: Long = 500000500000
scala> import scala.annotation.tailrec
scala> @tailrec
| def foldl[A,B](f: B=>A=>B)(e:B)(s:List[A]):B =
| s match { case Nil => e
| case x::xs => foldl(f)(f(e)(x))(xs) }
scala> def `(+)`: Long => Long => Long =
| m => n => m + n
scala> foldl(`(+)`)(0)(List.range(1,10_001))
val res1: Int = 50005000
scala> foldl(`(+)`)(0)(List.range(1,100_001))
val res2: Int = 705082704
scala> // same again but using built-in function
scala> List.range(1,10_001).foldLeft(0)(_+_)
val res36: Int = 50005000
scala> List.range(1,100_001).foldLeft(0)(_+_)
val res37: Long = 5000050000
The reason a stack overflow is not happening here is because built-in foldRight is defined in terms of foldLeft! (see cheat-sheet #7)
𝑓𝑜𝑙𝑑𝑟 ∷ 𝛼 → 𝛽 → 𝛽 → 𝛽 → 𝛼 → 𝛽
𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 = 𝑏
𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓 𝑥 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥𝑠
𝑓𝑜𝑙𝑑𝑙 ∷ 𝛽 → 𝛼 → 𝛽 → 𝛽 → 𝛼 → 𝛽
𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 = 𝑏
𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑓 𝑏 𝑥 𝑥𝑠
2.2.3 Tail recursion
The code of lengthS will fail for large enough sequences. To see why, consider an inductive definition of the .length method as a
function lengthS:
def lengthS(s: Seq[Int]): Int =
if (s.isEmpty) 0
else 1 + lengthS(s.tail)
scala> lengthS((1 to 1000).toList)
res0: Int = 1000
scala> val s = (1 to 100_000).toList
s : List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ...
scala> lengthS(s)
java.lang.StackOverflowError
at .lengthS(<console>:12)
at .lengthS(<console>:12)
at .lengthS(<console>:12)
at .lengthS(<console>:12)
...
The problem is not due to insufficient main memory: we are able to compute and hold in memory the entire sequence s. The
problem is with the code of the function lengthS. This function calls itself inside the expression 1 + lengthS(...). Let us
visualize how the computer evaluates that code:
lengthS(Seq(1, 2, ..., 100_000))
= 1 + lengthS(Seq(2, ..., 100_000))
= 1 + (1 + lengthS(Seq(3, ..., 100_000)))
= ...
Sergei Winitzki
sergei-winitzki-11a6431
The code of lengthS will evaluate the inductive step, that is, the “else” part of the “if/else”, about 100,000 times. Each time, the
intermediate sub-expression with nested computations 1+(1+(...)) will get larger.
That sub-expression needs to be held somewhere in memory, until the function body goes into the base case with no more
recursive calls. When that happens, the intermediate sub-expression will contain about 100_000_nested function calls still
waiting to be evaluated.
A special area of memory called stack memory is dedicated to storing the arguments for all not-yet-evaluated nested function
calls. Due to the way computer memory is managed, the stack memory has a fixed size and cannot grow automatically. So, when
the intermediate expression becomes large enough, it causes an overflow of the stack memory and crashes the program.
One way to avoid stack overflows is to use a trick called tail recursion. Using tail recursion means rewriting the code so that all
recursive calls occur at the end positions (at the “tails”) of the function body. In other words, each recursive call must be itself the
last computation in the function body, rather than placed inside other computations. Here is an example of tail-recursive code:
def lengthT(s: Seq[Int], res: Int): Int =
if (s.isEmpty) res
else lengthT(s.tail, res + 1)
In this code, one of the branches of the if/else returns a fixed value without doing any recursive calls, while the other branch
returns the result of a recursive call to lengthT(...).
It is not a problem that the recursive call to lengthT has some sub-expressions such as res + 1 as its arguments, because all
these sub-expressions will be computed before lengthT is recursively called.
def lengthS(s: Seq[Int]): Int =
if (s.isEmpty) 0
else 1 + lengthS(s.tail)
lengthS(Seq(1, 2, ..., 100_000))
= 1 + lengthS(Seq(2, ..., 100_000))
= 1 + (1 + lengthS(Seq(3, ..., 100_000)))
= ...
Sergei Winitzki
sergei-winitzki-11a6431
The recursive call to lengthT is the last computation performed by this branch of the if/else. A tail-recursive function can have many
if/else or match/case branches, with or without recursive calls; but all recursive calls must be always the last expressions returned.
The Scala compiler will always use tail recursion when possible. Additionally, Scala has a feature for verifying that a function’s code is
tail-recursive: the tailrec annotation. If a function with a tailrec annotation is not tail-recursive (or is not recursive at all), the
program will not compile. The code of lengthT with a tailrec annotation looks like this:
import scala.annotation.tailrec
@tailrec def lengthT(s: Seq[Int], res: Int): Int =
if (s.isEmpty) res
else lengthT(s.tail, res + 1)
Let us trace the evaluation of this function on an example:
lengthT(Seq(1,2,3), 0)
= lengthT(Seq(2,3), 0 + 1) // = lengthT(Seq(2,3), 1)
= lengthT(Seq(3), 1 + 1) // = lengthT(Seq(3), 2)
= lengthT(Seq(), 2 + 1) // = lengthT(Seq(), 3)
= 3
All sub-expressions such as 1 + 1 and 2 + 1 are computed before recursive calls to lengthT. Because of that, sub-expressions
do not grow within the stack memory. This is the main benefit of tail recursion.
How did we rewrite the code of lengthS into the tail-recursive code of lengthT? An important difference between lengthS and
lengthT is the additional argument (res), called the accumulator argument. This argument is equal to an intermediate result of the
computation. The next intermediate result (res + 1) is computed and passed on to the next recursive call via the accumulator
argument. In the base case of the recursion, the function now returns the accumulated result (res) rather than 0, because at that
time the computation is finished. Rewriting code by adding an accumulator argument to achieve tail recursion is called the
accumulator technique or the “accumulator trick”.
def lengthS(s: Seq[Int]): Int =
if (s.isEmpty) 0
else 1 + lengthS(s.tail)
Sergei Winitzki
sergei-winitzki-11a6431
One consequence of using the accumulator trick is that the function lengthT now always needs a value for the accumulator
argument. However, our goal is to implement a function such as length(s) with just one argument, s:Seq[Int]. We can define
length(s) = lengthT(s, ???) if we supply an initial accumulator value. The correct initial value for the accumulator is 0, since
in the base case (an empty sequence s) we need to return 0.
It appears useful to define the helper function (lengthT) separately. Then length will just call lengthT and specify the initial value
of the accumulator argument. To emphasize that lengthT is a helper function that is only used by length to achieve tail
recursion, we define lengthT as a nested function inside the code of length:
import scala.annotation.tailrec
def length[A](xs: Seq[A]): Int = {
@tailrec def lengthT(s: Seq[A], res: Int): Int = {
if (s.isEmpty) res
else lengthT(s.tail, res + 1)
}
lengthT(xs, 0)
}
When length is implemented like that, users will not be able to call lengthT directly, because lengthT is only visible within the
body of the length function. Another possibility in Scala is to use a default value for the res argument:
@tailrec def length(s: Seq[A], res: Int = 0): Int =
if (s.isEmpty) res
else length(s.tail, res + 1)
Giving a default value for a function argument is the same as defining two functions: one with that argument and one without. For
example, the syntax
def f(x: Int, y: Boolean = false): Int = ... // Function body.
Sergei Winitzki
sergei-winitzki-11a6431
is equivalent to defining two functions with the same name but different numbers of arguments:
def f(x: Int, y: Boolean) = ... // Define the function body here.
def f(x: Int): Int = f(Int, false) // Call the function defined above.
Using a default argument value, we can define the tail-recursive helper function and the main function at once, making the code
shorter.
The accumulator trick works in a large number of cases, but it may be not obvious how to introduce the accumulator argument,
what its initial value must be, and how to define the inductive step for the accumulator. In the example with the lengthT
function, the accumulator trick works because of the following mathematical property of the expression being computed:
1 + (1 + (1 + (... + 0))) = (((0 + 1) + 1) + ...) + 1 .
This equation follows from the associativity law of addition. So, the computation can be rearranged to group all additions to the
left. During the evaluation, the accumulator’s value corresponds to a certain number of left-grouped parentheses ((0 + 1) ...) +
1. In code, it means that intermediate expressions are fully computed before making recursive calls; So, recursive calls always
occur outside all other sub-expressions - that is, in tail positions. There are no sub-expressions that need to be stored on the stack
until all the recursive calls are complete.
However, not all computations can be rearranged in that way. Even if a code rearrangement exists, it may not be immediately
obvious how to find it.
Sergei Winitzki
sergei-winitzki-11a6431
Left Folds, Laziness, and Space Leaks
To keep our initial discussion simple, we use foldl throughout most of this section. This is convenient for testing, but we will
never use foldl in practice. The reason has to do with Haskell’s nonstrict evaluation. If we apply foldl (+) [1,2,3], it
evaluates to the expression (((0 + 1) + 2) + 3). We can see this occur if we revisit the way in which the function gets
expanded:
foldl (+) 0 (1:2:3:[])
== foldl (+) (0 + 1) (2:3:[])
== foldl (+) ((0 + 1) + 2) (3:[])
== foldl (+) (((0 + 1) + 2) + 3) []
== (((0 + 1) + 2) + 3)
The final expression will not be evaluated to 6 until its value is demanded. Before it is evaluated, it must be stored as a
thunk. Not surprisingly, a thunk is more expensive to store than a single number, and the more complex the thunked
expression, the more space it needs. For something cheap such as arithmetic, thunking an expression is more
computationally expensive than evaluating it immediately. We thus end up paying both in space and in time.
When GHC is evaluating a thunked expression, it uses an internal stack to do so. Because a thunked expression could
potentially be infinitely large, GHC places a fixed limit on the maximum size of this stack. Thanks to this limit, we can try
a large thunked expression in ghci without needing to worry that it might consume all the memory:
ghci> foldl (+) 0 [1..1000]
500500
From looking at this expansion, we can surmise that this creates a thunk that consists of 1,000 integers and 999 applications
of (+). That’s a lot of memory and effort to represent a single number! With a larger expression, although the size is still
modest, the results are more dramatic:
ghci> foldl (+) 0 [1..1000000]
*** Exception: stack overflow
On small expressions, foldl will work correctly but slowly, due to the thunking_overhead that it incurs.
Bryan O’Sullivan
John Goerzen
Donald Bruce Stewart
We refer to this invisible thunking as a space leak, because our code is operating normally, but it is using far more memory
than it should.
On larger expressions, code with a space leak will simply fail, as above. A space leak with foldl is a classic roadblock for new
Haskell programmers. Fortunately, this is easy to avoid.
The Data.List module defines a function named foldl' that is similar to foldl, but does not build up thunks. The difference in
behavior between the two is immediately obvious:
ghci> foldl (+) 0 [1..1000000]
*** Exception: stack overflow
ghci> :module +Data.List
ghci> foldl' (+) 0 [1..1000000]
500000500000
Due to foldl’s thunking behavior, it is wise to avoid this function in real programs, even if it doesn’t fail outright, it will be
unnecessarily inefficient. Instead, import Data.List and use foldl'.
Bryan O’Sullivan
John Goerzen
Donald Bruce Stewart
https://fpilluminated.com/
based
on

More Related Content

Folding Cheat Sheet #6 - sixth in a series

  • 1. CHEAT-SHEET Folding #6 ∶ / 𝒂𝟎 ∶ / 𝒂𝟏 ∶ / 𝒂𝟐 ∶ / 𝒂𝟑 𝒇 / 𝒂𝟎 𝒇 / 𝒂𝟏 𝒇 / 𝒂𝟐 𝒇 / 𝒂𝟑 𝒆 @philip_schwarz slides by https://fpilluminated.com/
  • 2. 𝑓𝑜𝑙𝑑𝑙 is tail recursive. Folding a list (however large) with 𝑓𝑜𝑙𝑑𝑙 does not result in a stack overflow error. ‡ 𝑓𝑜𝑙𝑑𝑟 is not tail recursive. ☩ Folding a sufficiently large list with 𝑓𝑜𝑙𝑑𝑟 results in a stack overflow error. † † See slide after next for an exception in Scala ‡ See next slide for an exception in Haskell ☩ See slides five to nine for a refresher on tail recursion 𝑓𝑜𝑙𝑑𝑙 ∷ 𝛽 → 𝛼 → 𝛽 → 𝛽 → 𝛼 → 𝛽 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 = 𝑏 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑓 𝑏 𝑥 𝑥𝑠 𝑓𝑜𝑙𝑑𝑟 ∷ 𝛼 → 𝛽 → 𝛽 → 𝛽 → 𝛼 → 𝛽 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 = 𝑏 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓 𝑥 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥𝑠
  • 3. 𝑓𝑜𝑙𝑑𝑟 ∷ 𝛼 → 𝛽 → 𝛽 → 𝛽 → 𝛼 → 𝛽 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 = 𝑏 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓 𝑥 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥𝑠 𝑓𝑜𝑙𝑑𝑙 ∷ 𝛽 → 𝛼 → 𝛽 → 𝛽 → 𝛼 → 𝛽 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 = 𝑏 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑓 𝑏 𝑥 𝑥𝑠 > :{ | foldRight :: (α -> β -> β) -> β -> [α] -> β | foldRight f e [] = e | foldRight f e (x:xs) = f x (foldRight f e xs) | :} > foldRight (+) 0 [1..10000000] 50000005000000 > foldRight (+) 0 [1..100000000] *** Exception: stack overflow > -- same again but using built-in function > foldr (+) 0 [1..10000000] 50000005000000 > foldr (+) 0 [1..100000000] *** Exception: stack overflow > :{ | foldLeft :: (β -> α -> β) -> β -> [α] -> β | foldLeft f e [] = e | foldLeft f e (x:xs) = foldLeft f (f e x) xs | :} > foldLeft (+) 0 [1..10000000] 50000005000000 > foldLeft (+) 0 [1..100000000] *** Exception: stack overflow > -- same again but using built-in function > foldl (+) 0 [1..10000000] 50000005000000 > foldl (+) 0 [1..100000000] *** Exception: stack overflow > Data.List.foldl' (+) 0 [1..100000000] 5000000050000000 These stack overflows have to do with Haskell’s nonstrict evaluation, and are avoided using a strict left fold, called foldl’ (see final slides).
  • 4. scala> def foldr[A,B](f: A=>B=>B)(e:B)(s:List[A]):B = | s match { case Nil => e | case x::xs => f(x)(foldr(f)(e)(xs)) } scala> def `(+)`: Long => Long => Long = | m => n => m + n scala> foldr(`(+)`)(0)(List(1,2,3,4)) val res1: Int = 10 scala> foldr(`(+)`)(0)(List.range(1,10_001)) val res2: Int = 50005000 scala> foldr(`(+)`)(0)(List.range(1,100_001)) java.lang.StackOverflowError scala> // same again but using built-in function scala> List.range(1,10_001).foldRight(0)(_+_) val res3: Int = 50005000 scala> List.range(1,100_001).foldRight(0L)(_+_) val res4: Long = 500000500000 scala> import scala.annotation.tailrec scala> @tailrec | def foldl[A,B](f: B=>A=>B)(e:B)(s:List[A]):B = | s match { case Nil => e | case x::xs => foldl(f)(f(e)(x))(xs) } scala> def `(+)`: Long => Long => Long = | m => n => m + n scala> foldl(`(+)`)(0)(List.range(1,10_001)) val res1: Int = 50005000 scala> foldl(`(+)`)(0)(List.range(1,100_001)) val res2: Int = 705082704 scala> // same again but using built-in function scala> List.range(1,10_001).foldLeft(0)(_+_) val res36: Int = 50005000 scala> List.range(1,100_001).foldLeft(0)(_+_) val res37: Long = 5000050000 The reason a stack overflow is not happening here is because built-in foldRight is defined in terms of foldLeft! (see cheat-sheet #7) 𝑓𝑜𝑙𝑑𝑟 ∷ 𝛼 → 𝛽 → 𝛽 → 𝛽 → 𝛼 → 𝛽 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 = 𝑏 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓 𝑥 𝑓𝑜𝑙𝑑𝑟 𝑓 𝑏 𝑥𝑠 𝑓𝑜𝑙𝑑𝑙 ∷ 𝛽 → 𝛼 → 𝛽 → 𝛽 → 𝛼 → 𝛽 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 = 𝑏 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑏 𝑥: 𝑥𝑠 = 𝑓𝑜𝑙𝑑𝑙 𝑓 𝑓 𝑏 𝑥 𝑥𝑠
  • 5. 2.2.3 Tail recursion The code of lengthS will fail for large enough sequences. To see why, consider an inductive definition of the .length method as a function lengthS: def lengthS(s: Seq[Int]): Int = if (s.isEmpty) 0 else 1 + lengthS(s.tail) scala> lengthS((1 to 1000).toList) res0: Int = 1000 scala> val s = (1 to 100_000).toList s : List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ... scala> lengthS(s) java.lang.StackOverflowError at .lengthS(<console>:12) at .lengthS(<console>:12) at .lengthS(<console>:12) at .lengthS(<console>:12) ... The problem is not due to insufficient main memory: we are able to compute and hold in memory the entire sequence s. The problem is with the code of the function lengthS. This function calls itself inside the expression 1 + lengthS(...). Let us visualize how the computer evaluates that code: lengthS(Seq(1, 2, ..., 100_000)) = 1 + lengthS(Seq(2, ..., 100_000)) = 1 + (1 + lengthS(Seq(3, ..., 100_000))) = ... Sergei Winitzki sergei-winitzki-11a6431
  • 6. The code of lengthS will evaluate the inductive step, that is, the “else” part of the “if/else”, about 100,000 times. Each time, the intermediate sub-expression with nested computations 1+(1+(...)) will get larger. That sub-expression needs to be held somewhere in memory, until the function body goes into the base case with no more recursive calls. When that happens, the intermediate sub-expression will contain about 100_000_nested function calls still waiting to be evaluated. A special area of memory called stack memory is dedicated to storing the arguments for all not-yet-evaluated nested function calls. Due to the way computer memory is managed, the stack memory has a fixed size and cannot grow automatically. So, when the intermediate expression becomes large enough, it causes an overflow of the stack memory and crashes the program. One way to avoid stack overflows is to use a trick called tail recursion. Using tail recursion means rewriting the code so that all recursive calls occur at the end positions (at the “tails”) of the function body. In other words, each recursive call must be itself the last computation in the function body, rather than placed inside other computations. Here is an example of tail-recursive code: def lengthT(s: Seq[Int], res: Int): Int = if (s.isEmpty) res else lengthT(s.tail, res + 1) In this code, one of the branches of the if/else returns a fixed value without doing any recursive calls, while the other branch returns the result of a recursive call to lengthT(...). It is not a problem that the recursive call to lengthT has some sub-expressions such as res + 1 as its arguments, because all these sub-expressions will be computed before lengthT is recursively called. def lengthS(s: Seq[Int]): Int = if (s.isEmpty) 0 else 1 + lengthS(s.tail) lengthS(Seq(1, 2, ..., 100_000)) = 1 + lengthS(Seq(2, ..., 100_000)) = 1 + (1 + lengthS(Seq(3, ..., 100_000))) = ... Sergei Winitzki sergei-winitzki-11a6431
  • 7. The recursive call to lengthT is the last computation performed by this branch of the if/else. A tail-recursive function can have many if/else or match/case branches, with or without recursive calls; but all recursive calls must be always the last expressions returned. The Scala compiler will always use tail recursion when possible. Additionally, Scala has a feature for verifying that a function’s code is tail-recursive: the tailrec annotation. If a function with a tailrec annotation is not tail-recursive (or is not recursive at all), the program will not compile. The code of lengthT with a tailrec annotation looks like this: import scala.annotation.tailrec @tailrec def lengthT(s: Seq[Int], res: Int): Int = if (s.isEmpty) res else lengthT(s.tail, res + 1) Let us trace the evaluation of this function on an example: lengthT(Seq(1,2,3), 0) = lengthT(Seq(2,3), 0 + 1) // = lengthT(Seq(2,3), 1) = lengthT(Seq(3), 1 + 1) // = lengthT(Seq(3), 2) = lengthT(Seq(), 2 + 1) // = lengthT(Seq(), 3) = 3 All sub-expressions such as 1 + 1 and 2 + 1 are computed before recursive calls to lengthT. Because of that, sub-expressions do not grow within the stack memory. This is the main benefit of tail recursion. How did we rewrite the code of lengthS into the tail-recursive code of lengthT? An important difference between lengthS and lengthT is the additional argument (res), called the accumulator argument. This argument is equal to an intermediate result of the computation. The next intermediate result (res + 1) is computed and passed on to the next recursive call via the accumulator argument. In the base case of the recursion, the function now returns the accumulated result (res) rather than 0, because at that time the computation is finished. Rewriting code by adding an accumulator argument to achieve tail recursion is called the accumulator technique or the “accumulator trick”. def lengthS(s: Seq[Int]): Int = if (s.isEmpty) 0 else 1 + lengthS(s.tail) Sergei Winitzki sergei-winitzki-11a6431
  • 8. One consequence of using the accumulator trick is that the function lengthT now always needs a value for the accumulator argument. However, our goal is to implement a function such as length(s) with just one argument, s:Seq[Int]. We can define length(s) = lengthT(s, ???) if we supply an initial accumulator value. The correct initial value for the accumulator is 0, since in the base case (an empty sequence s) we need to return 0. It appears useful to define the helper function (lengthT) separately. Then length will just call lengthT and specify the initial value of the accumulator argument. To emphasize that lengthT is a helper function that is only used by length to achieve tail recursion, we define lengthT as a nested function inside the code of length: import scala.annotation.tailrec def length[A](xs: Seq[A]): Int = { @tailrec def lengthT(s: Seq[A], res: Int): Int = { if (s.isEmpty) res else lengthT(s.tail, res + 1) } lengthT(xs, 0) } When length is implemented like that, users will not be able to call lengthT directly, because lengthT is only visible within the body of the length function. Another possibility in Scala is to use a default value for the res argument: @tailrec def length(s: Seq[A], res: Int = 0): Int = if (s.isEmpty) res else length(s.tail, res + 1) Giving a default value for a function argument is the same as defining two functions: one with that argument and one without. For example, the syntax def f(x: Int, y: Boolean = false): Int = ... // Function body. Sergei Winitzki sergei-winitzki-11a6431
  • 9. is equivalent to defining two functions with the same name but different numbers of arguments: def f(x: Int, y: Boolean) = ... // Define the function body here. def f(x: Int): Int = f(Int, false) // Call the function defined above. Using a default argument value, we can define the tail-recursive helper function and the main function at once, making the code shorter. The accumulator trick works in a large number of cases, but it may be not obvious how to introduce the accumulator argument, what its initial value must be, and how to define the inductive step for the accumulator. In the example with the lengthT function, the accumulator trick works because of the following mathematical property of the expression being computed: 1 + (1 + (1 + (... + 0))) = (((0 + 1) + 1) + ...) + 1 . This equation follows from the associativity law of addition. So, the computation can be rearranged to group all additions to the left. During the evaluation, the accumulator’s value corresponds to a certain number of left-grouped parentheses ((0 + 1) ...) + 1. In code, it means that intermediate expressions are fully computed before making recursive calls; So, recursive calls always occur outside all other sub-expressions - that is, in tail positions. There are no sub-expressions that need to be stored on the stack until all the recursive calls are complete. However, not all computations can be rearranged in that way. Even if a code rearrangement exists, it may not be immediately obvious how to find it. Sergei Winitzki sergei-winitzki-11a6431
  • 10. Left Folds, Laziness, and Space Leaks To keep our initial discussion simple, we use foldl throughout most of this section. This is convenient for testing, but we will never use foldl in practice. The reason has to do with Haskell’s nonstrict evaluation. If we apply foldl (+) [1,2,3], it evaluates to the expression (((0 + 1) + 2) + 3). We can see this occur if we revisit the way in which the function gets expanded: foldl (+) 0 (1:2:3:[]) == foldl (+) (0 + 1) (2:3:[]) == foldl (+) ((0 + 1) + 2) (3:[]) == foldl (+) (((0 + 1) + 2) + 3) [] == (((0 + 1) + 2) + 3) The final expression will not be evaluated to 6 until its value is demanded. Before it is evaluated, it must be stored as a thunk. Not surprisingly, a thunk is more expensive to store than a single number, and the more complex the thunked expression, the more space it needs. For something cheap such as arithmetic, thunking an expression is more computationally expensive than evaluating it immediately. We thus end up paying both in space and in time. When GHC is evaluating a thunked expression, it uses an internal stack to do so. Because a thunked expression could potentially be infinitely large, GHC places a fixed limit on the maximum size of this stack. Thanks to this limit, we can try a large thunked expression in ghci without needing to worry that it might consume all the memory: ghci> foldl (+) 0 [1..1000] 500500 From looking at this expansion, we can surmise that this creates a thunk that consists of 1,000 integers and 999 applications of (+). That’s a lot of memory and effort to represent a single number! With a larger expression, although the size is still modest, the results are more dramatic: ghci> foldl (+) 0 [1..1000000] *** Exception: stack overflow On small expressions, foldl will work correctly but slowly, due to the thunking_overhead that it incurs. Bryan O’Sullivan John Goerzen Donald Bruce Stewart
  • 11. We refer to this invisible thunking as a space leak, because our code is operating normally, but it is using far more memory than it should. On larger expressions, code with a space leak will simply fail, as above. A space leak with foldl is a classic roadblock for new Haskell programmers. Fortunately, this is easy to avoid. The Data.List module defines a function named foldl' that is similar to foldl, but does not build up thunks. The difference in behavior between the two is immediately obvious: ghci> foldl (+) 0 [1..1000000] *** Exception: stack overflow ghci> :module +Data.List ghci> foldl' (+) 0 [1..1000000] 500000500000 Due to foldl’s thunking behavior, it is wise to avoid this function in real programs, even if it doesn’t fail outright, it will be unnecessarily inefficient. Instead, import Data.List and use foldl'. Bryan O’Sullivan John Goerzen Donald Bruce Stewart